Using the old SDK, when I called my server API with my access token, on the server I would also have a user object with nickname, email, and picture. After updating to this library, that data is no longer available. My server code relies on this. Is there something wrong with my configuration? A bug in this library? A breaking change that no longer makes this info available?
Any assistance would be great.
@EisenbergEffect Hi 👋 Access tokens are opaque, which means they're just strings and you shouldn't rely on them being anything (a JWT, for example). More information here (http://www.cloudidentity.com/blog/2018/04/20/clients-shouldnt-peek-inside-access-tokens/) and in a very well written comment on this here.
With all that being said, when you use a custom audience during the authentication, Auth0 will send you an access token that is a valid JWT that you can decode if you need to. All you need to do is provide your custom audience when initializing the client:
const auth0 = await createAuth0Client({
domain: 'AUTH0_DOMAIN',
client_id: 'AUTH0_CLIENT_ID',
audience: 'CUSTOM_AUDIENCE'
});
Let's try that and see how it goes!
I’ve got the audience setup and auth is happening fine. No problem there. It’s just that previously, when I called my API with the access token, using expressJwt, the request would then have a user object decoded with nickname, email, and picture properties. Now, the user object only has sub and related properties. The email and profile data are no longer present.
It seems to me that when using the previous SDK, (where I didn’t have a custom audience), Auth0 included the profile and email data in the token, such that the server could decode that. But, it appears it’s no longer doing that. If this is by design, what are my solutions? For example, do I need to create a custom rule that adds this data into the token manually?
So, just to make sure: you're receiving a JWT as an access token, but instead of having all the properties you expected, you're only getting {sub}.
Let's try to idenfity if the server is behaving differently or what. Thanks for your patience!
Yes, I do get the JWT as an access token. However, on the server, I am missing email, nickname, and picture, which were there before I switched over to the new SDK. I haven’t created any new clients. It’s my same SPA and my same Auth0 app. However, as part of the tutorial on the new SDK, I did end up creating an API. I didn’t have an API before.
Here’s the code that I’ve got on the client-side:
function configureClient() {
return createAuth0Client({
domain: ‘my-domain’,
client_id: ‘my-client-id’,
audience: ‘my-audience’
});
}
function startApp(auth) {
// do some app specific startup here
}
window.addEventListener('load', function() {
configureClient()
.then(function(auth0) {
auth0.isAuthenticated()
.then(function(isAuthenticated) {
if (isAuthenticated) {
startApp(auth0);
return;
}
var query = window.location.search;
if (query.includes("code=") && query.includes("state=")) {
auth0.handleRedirectCallback()
.then(function() {
var returnUrl = localStorage.getItem('returnUrl');
if (returnUrl) {
localStorage.removeItem('returnUrl');
history.replaceState(null, null, returnUrl);
}
startApp(auth0);
});
} else {
localStorage.setItem('returnUrl', location.href);
auth0.loginWithRedirect({
redirect_uri: window.location.origin
});
}
});
});
});
Here’s the client code that sets up my fetch call to my API:
private async fetchOptions(options: RequestInit = {}): Promise<RequestInit> {
const token = await this.auth.getTokenSilently();
return {
...options,
mode: 'cors',
headers: {
'Authorization': `Bearer ${token}`
}
};
}
Here’s the code that I have in my server API auth decorator, which is wrapping my route handler in my Azure Functions:
const domain = process.env.Auth0Domain;
const expressValidateJwt = expressJwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${domain}/.well-known/jwks.json`
}),
audience: process.env.Auth0Audience,
issuer: `https://${domain}/`,
algorithms: ['RS256']
});
const getJwtValidationError = err => {
return {
status: err.status || 500,
body: {
message: err.message
}
};
};
export const auth = (next) => {
return (context, req, ...rest) => {
return new Promise(resolve => {
expressValidateJwt(req, null, err => {
if (err) {
return resolve(getJwtValidationError(err));
}
const rawUser = req.user;
const user: IUser = {
type: 'user',
id: rawUser.sub.replace('auth0|', ''),
name: rawUser.nickname, // no longer present
email: rawUser.email, // no longer present
avatarUrl: rawUser.picture // no longer present
};
req.user = user;
resolve(next(context, req, ...rest));
});
});
};
};
The user object I’m getting has this shape:
{
iss: string,
sub: string,
aud: string[],
iat: number,
exp: number,
azp: string,
scope: 'openid profile email'
}
It used to contain nickname, email, and picture as well, but they are no longer present.
@EisenbergEffect Thanks! Do you have the code snippet on how you did the same thing with Auth0.js?
By a fluke of my gitignore file, I don't have the exact code I was using with the original SDK. I pretty much followed the tutorial on that exactly. The one difference I do remember is that I had the audience set up to point to my client id or something like that. I never created an Auth0 API. I tried this same setup with the new SDK and it didn't work. I got errors from Auth0.
@EisenbergEffect That snippet would help a lot, but let's try to figure it out in another way.
I did a few tests locally using Auth0.js and auth0-spa-js using a custom audience and the behavior is the same: the access token doesn't have the identity information. The only way to add that is using a rule. Is it possible that you were using an id_token instead? This would make more sense because the id_token carries all the identity information like nickname, email, picture etc.
@luisrudge It does seem likely that I was using an id_token. Is there a way to get that on the client and send that along instead of the access_token (assuming it also includes the access_token data) or would you recommend that creating a custom rule to add these fields to my access_token instead?
@EisenbergEffect thanks for the clarification! Currently, we don't expose the raw id_token because, as I said before, the id_token is only meant to be consumed by your client (which is your SPA). So, your best bet right now, is to create a rule to add the claims you need to the access_token and use that (remember to scope your claims).
function (user, context, callback) {
const namespace = 'https://myapp.example.com/';
context.accessToken[namespace + 'nickname’] = user.nickname;
callback(null, user, context);
}
With that being said, we're discussing internally with a few teams if we should expose the id_token or not. There's an amazing video explaining the dilemma of this decision here: https://auth0.com/docs/videos/learn-identity/04-calling-an-api#wistia_yw6hmdhnft?time=2126. The entire video is amazing, but I linked to the precise time where Vittorio explains when it's ok to use an id_token to interact with your API (spoiler: when they're both part of the same logic application, like when you create your API only to satisfy your SPA).
So, if you want to fix this right now, use a custom rule and add the data you need in your application. If we decide to expose the id_token in the SDK, I'll ping this issue so you and everyone else having the same issue are aware that there's a new way to do it.
I'll close this issue but feel free to send me any questions you might have. 🎉
That makes sense. In my case, the API exists solely for the SPA, so the id_token would be useful. However, since writing the code to add the custom claims is not a big deal (per the example above), that seems reasonable to me as well. This might be a good bit for the FAQ. Thanks for the help. I'll give the custom rule a try this evening and see how that works out 😄
Just circling back around, I was able to get things working nicely with custom rules that add scoped claims. Thanks again!
@EisenbergEffect perfect! Hit us up if you have any issues!
Most helpful comment
I’ve got the audience setup and auth is happening fine. No problem there. It’s just that previously, when I called my API with the access token, using
expressJwt, the request would then have auserobject decoded withnickname,email, andpictureproperties. Now, the user object only hassuband related properties. The email and profile data are no longer present.