JWT token is "invalid signature"?

I’m totally new to JWT and most auth.

Right now I have a front-end which has Auth0’s universal login integrated. On callback, I obtain the auth token via auth0Client.getTokenSilently().

This token is 505 characters long, and contains a mixture of alphanumeric characters and .'s.

I pass this to my back-end API so it can determine whether the user is logged in before completing the requested action.

Firstly, is this the right flow?

Secondly, why is JWT.io’s linter telling me the JWT I’m giving it is an “invalid signature”? This results in it showing an empty payload.

Despite this, when my back-end uses this 505-char token to retireve the user’s profile, it does bring back the profile.

What am I missing here - why does everything basically work, but JWT.io says the token’s signature is invalid, resulting in no payload shown?

Hey again!

You’re on the right track!

My guess is that this token is missing the audience - If you do not specify an audience (aud claim) then the access token you get back will be opaque (not a jwt). That is, it cannot be decoded but can be used against the /userinfo endpoint. Some more on that here:

And a helpful FAQ on audience in general:

Hope this helps!

I’m mightily confused. I don’t have a hand in generating that token - it’s the result of resolving the query string params sent to my callback URL. I thought that what I resolved it to (via getTokenSilently()) generated a JWT. But you’re saying it’s lacking something, so how do I stop it lacking that thing?

I’m trying to follow docs but they go on forever and it just becomes circuitous. I did try to follow this video, but when I installed the debugger extension and stubbed the login request, I was met with an error page saying something went wrong, we track these errors automatically etc.

To clarify my issue: how do I validate, on the back-end, the token generated by getTokenSilently(), if it’s not a JWT after all because it’s missing this aud thing?

This page suggests that, in my situation, what I’m actually generating is not a JWT but an “opaque access token”. Accordingly, this doesn’t need validating, merely authenticating against the /userinfo endpoint. So if I understand that article correctly, authentication for my case is as simple as just calling that endpoint. If I get a profile, the user is auth’d; if I don’t, they’re not. But you mentioned above that wasn’t ideal, so I’m confused.

Thank you.


Hey @kkrp1 !

Again, this all comes down to the audience - This would be passed to getTokenSilently via authorizationParams.

If the only thing you are doing is using the access token to call /userinfo then you are correct in that you do not need an audience, nor do you need to validate the token per se. In most cases, the access token is used to access an API (your API) and in that case you’d want a JWT to inspect which does require an audience (your API identifier in Auth0) as you’ve seen.


@tyf is spot on; that’s exactly what’s happening :slight_smile:

Auth0 typically generates both an ID Token and an Access Token: the ID Token being designed for use by an application in order to build the application level session; the Access Token being designed for use by your own API. Without the OAuth 2 audience parameter, the (Access) Token returned is designed only for use with the Auth0 /userinfo endpoint (here). Whilst it is a JWT, it uses an alternate encoding algorithm so is effectively opaque, and not designed to be decoded by jwt.io.

Typically, any Access Token you pass to your own API should have been generated by defining a (custom) API in Auth0 - see here for more details. You will typically specify a custom audience (a.k.a. Identifier) for your API and also typically define scopes; both of which your API will typically validate. The Access Tokens generated for the (custom) audience can then be used to call your API securely and your API can also use it to call the Auth0 /userinfo endpoint too: all (custom) API tokens generated in Auth0 typically support two intended audiences, the custom one you define and also the Auth0 /userinfo endpoint.

Hope that helps :sunglasses:


Thank you, both. I think the reality is I’m just rather out of my depth. I feel like I’m patching many and various concepts together from different docs articles and trying to make it all work. I appreciate all the advice that’s been given.

So here is what I have now done, based on the latest advice.

  1. I have set up an API under Applications > APIs. I have given it the identifier https://my-api

  2. I am now passingthe audience param to getTokenSilently() via getTokenSilently({audience: 'https://my-api'}).

However this results in no change. The access token I get back from getTokenSilently(), even though I’m now specifying the audience, is still not a valid JWT according to JWT.io and thus not something I can validate back-end.

This article suggests I must specify audience (my API identifier) in the Auth0 constructor. I’ve tried this too, but same result.

const auth0Client = await auth0.createAuth0Client({
	domain: '********',
	clientId: '********',
	audience: 'https://my-api',

It’s also not clear which Auth0 application my browser should be connecting to. I have a SPA application, but when I created the API, this implicitly created a second (machine-to-machine) application. Only the former works with the browser flow. Yet presumably my API works only with the latter?

[[[ ---- EDIT ---- ]]]

Whoop! I finally got it working. It seems that I needed to specify audience within the authorizationParams sub-object in the constructor, i.e.

const auth0Client = await auth0.createAuth0Client({
	domain: '********',
	clientId: '********',
	authorizationParams: {
		redirect_uri: window.location.origin,
		audience: 'https://my-api'

I’d still be interested to know which application I should be using, though - the one I set up (the SPA application) or the one that was implcitly created (machine-to-machine) when I created my API. And how come, when I go to my API > Test, it lets me test it only in the context of the machine-to-machine application, yet my browser login works only with the SPA application client ID?

Continued thanks!

1 Like

@peter.fernandez @tyf any thoughts here re: my last paragraph? Many thanks.

Thanks for the update @kkrp1 and I’m really glad to hear you got things working! :sunglasses::tada:

OAuth2 defines a number of different grant types, which are also supported by Auth0 - see here for more details. Grant types essentially define what workflows applications can perform - and the context in which they perform them too: authorization code, for example, lets an application perform API operations in the context of a user - with that user’s consent - whilst client credentials allows an application to perform API operations in the context of the application itself (i.e. without a user ever being involved). The latter is typically used in a secure backend context - so rarely, if ever, in a SPA - and essentially uses machine level credentials rather than user level credentials. Auth0 automatically creates a machine-to-machine application for convenience, and this can be used to test an API from some secure automated backend (or the like).

1 Like

You’re welcome; glad to be of help :slightly_smiling_face: If you haven’t found it yet then may I suggest heading on over to https://developer.auth0.com where you can find a whole host of guides et al that are supplemental to our Auth0 Docs. You may find some additional material that might help you get a better understanding :sunglasses:


Thanks for this! I have found those guiys, and I’ve worked my way through several of them.

OK so based on what you said, does it sound like I’m on the right lines? To clarify, I’m not using the API key from my SPA; I’m using that from my back-end API to talk to Auth0 to get the profile of the user who logged in.

My precise flow is this:

  1. In the front-end, user clicks login and goes through the universal login flow (bouncing out to Auth0, then back to my page via the callback)

  2. This results in a code passed in the query string. This I convert to a JWT via the getTokenSilently() method of the front-end JS SDK. In doing this, I also specify the audience (which is the identifier of my Auth0-side API)

  3. From this point, any calls to my back-end API (which lives separately from the SPA) involves passing this JWT along with it, as a header.

  4. My API receives this, validates it via the express-oauth2-jwt-bearer NPM package, then, if that passes, uses it call /userinfo to resolve the user to a profile, get user meta etc.

This is my auth flow, based on everything I’ve learned and all the kind help I’ve received. Am I broadly on the right lines here?

Continued thanks.

Essentially that’s correct :sunglasses: In terms of step 4, the call to /userinfo will typically return the user profile as defined by the OAuth 2 specification. However this will typically not include any user metadata - user metadata being an Auth0 artefact - unless an aspect has been included as a custom claim within the ID Token: custom claims are available via the /userinfo endpoint. For additional details see JSON Web Token Claims


Thanks for this.

Re: meta data, actually I had a separate thread about this and your colleague suggested using a post-login action to bolt the meta data onto the returned profile. (Interesting, though, that this approach isn’t discussed on the user meta docs page, which does discuss some other approaches.)

Am I right in thinking the right thing is to retrieve the user meta from the profile, rather than somehow adding the user meta into the JWT itself? (My JWT noobness showing through, there…)

That really depends :slight_smile:

It depends on the metadata you want to retrieve. For example, retrieving user user_metadata directly from Auth0 can be done from an insecure - i.e. SPA - context via use of the Auth0 Management API by using a token allocated in a user context; see here for more details. Retrieving user app_metadata, however, typically requires a secure backend context using a token allocated via machine level authentication (i.e. client credentials grant).

It also depends on the contents of the metadata you intend to retrieve. If it’s of a PII or highly security sensitive nature then using the Management API can be the preference. However, this requires an additional API call to Auth0, (potentially) the use of a secure backend, and potentially may be impacted by rate limiting (see paragraph below). So using custom claims can be a more desirable option - particularly where (meta) data is of a non-PII or a lesser security sensitive nature (and when generated tokens are also managed securely, such as via a regular web service backend, or the like). Using custom claims doesn’t necessarily require any additional API calls to Auth0 - at least not outside of the /userinfo endpoint - and their use is not subject to rate limiting either.

And it also depends on the frequency that metadata will be retrieved. The rate limits associated with the Auth0 Management API are significantly different - and typically more restrictive - than the rate limits associated with the Auth0 Authentication API. See here for more details. Depending on how often metadata needs to be read, this can preclude use of the Auth0 Management API and so using custom claims may be the only option.

Lastly, the database that backs the Auth0 User Profile - i.e. where metadata et al is stored for a user; see User Profile Structure for additional details - is not designed to be general purpose. It is primarily designed to hold information that would be valuable as part of user authorization in the context of OAuth 2/OIDC, SAML, etc. So having some additional and external datastore, where specific user profile information is stored - such as the llist of recently placed orders, for instance - is the preferred alternative.

FWIW, Auth0 Professional Services (here) is often the best route to take for any integration where there may be aspects of a tricky nature - and/or where there are concerns about security or the potential for opening up additional security attack surfaces.


Thanks for this extensive info.

In my case, using the flow I described previously, my idea is to store permissions info on the user metadata (not to be confused with Auth0 permissions - I’m storing the permissions in a proprietary, JSON-formatted way). To be clear, these are permissions for using my back-end app, not using Auth0.

Given what you said about the Auth0 user database not being intended for general purpose, is this a bad idea, and instead, as you allude, I should store these permissions outside of Auth0?

These permissions need to be retrieved on every request to my back-end API. (I could cache them, but I’m trying to build my API statelessly). Since they come from /user_info, i.e. the authentication API, not the management API, I figure I’ll be OK with rate limiting.


That really depends on the complexity of the permission list you’re planning to store :slightly_smiling_face: If you do end up going down the metadata route, then I would recommend using user app_metadata; user user_metadata is user self-modifiable, which is typically something you would definitely not want users to be able to do! Alternatively, perhaps take a look at something like the Auth0 FGA project? It might be just what you’re looking for - or if not, at least give you some ideas :sunglasses:

These permissions need to be retrieved on every request to my back-end API. (I could cache them, but I’m trying to build my API statelessly). Since they come from /user_info , i.e. the authentication API, not the management API, I figure I’ll be OK with rate limiting.

This sort of fine/finer grained authorization use case is not uncommon. And for these scenarios the recommendation would typically be to add the authorization permissions as one or more custom claims to the Access Token JWT. Assuming the permission complexity won’t end up bloating the JWT that is. You can also add the same custom claims - or some additional (custom) claims based on the permissions - to the ID Token JWT too, if the UX needs to reflect what the user is allowed to do. Remember JWTs are signed, so it’s impossible to tamper with the data. And because JWT contents is easily (machine) decoded, you mitigate any additional API calls (e.g. the call to the /userinfo endpoint from step 4 in the flow above) :sunglasses:


Thanks as ever, @peter.fernandez, really appreciate the continued help.

Re: adding my proprietary permissions format to the JWT itself (to the payload), this is where I’m stuck. I can’t figure out how to add anything to the JWT’s payload - only to the response returned by /userinfo.

One of your colleagues suggested this action, but as I say, that just adds the metadata to the response from /userinfo, not the JWT payload itself:

exports.onExecutePostLogin = async (event, api) => {
  const namespace = 'https://my-app.example.com';
  if (event.authorization)
    api.idToken.setCustomClaim(`user_meta`, event.user.user_metadata);

I’d love to figure out how to add this (or any other info) to the JWT itself - that way I wouldn’t have to keep calling /userinfo on every request to my back-end to get the permissions, I’d have them right there in the JWT.

Again, apologies if I’m asking things you believe you’ve already answered - a lot of this is double-dutch to me and I’m trying to make sense of it as I go.


1 Like

Ah! I think I see where the confusion lies! Let me see if I can clarify :sunglasses:

Firstly I’d recommend reframing your perspective. Previously in the thread (see here for details) I mentioned that Auth0 typically generates two types of token: an ID Token and an Access Token; the former intended for use by an application, the latter intended for use by an API (a.k.a. resource server). They are both JWTs! Context then becomes an important clarifier when one simply refers to “a JWT” - because one has to understand/convey the use for which said JWT is intended. Thinking/communicating in terms of ID Token and Access Token on the other hand implicitly provides better clarity by virtue of nomenclature :slight_smile:

With this in mind, if we revisit the aforementioned code then - together with the Auth0 Docs for Actions Triggers: post-login - Event Object and Actions Triggers: post-login - API Object - we could perhaps refactor in the following manner:

 exports.onExecutePostLogin = async (event, api) => {
  const namespace = 'https://my-app.example.com';
  if (event.authorization)
    api.idToken.setCustomClaim(`user_meta`, event.user.user_metadata);
    api.accessToken.setCustomClaim(`user_meta`, event.user.user_metadata);

The original line (namelyapi.idToken.setCustomClaim) adds a custom claim to the ID Token which the application can use - i.e. to perhaps correctly format the UX. And the new line (namely api.accessToken.setCustomClaim) adds a custom claim to the Access Token which the API can then use - e.g. to perform any ‘fine grained’ authorization, or the like. Both are a, and in this case the same, custom claim added to a JWT!

Some best practice recommendations (and in no particular order):

  1. Prefer to create name-spaced custom claims (see here for further details). This is what the namespace constant declared is intended for, and one would simply use it like so: api.idToken.setCustomClaim(namespace+/user_meta, event.user.user_metadata);

  2. As per my previous post here: prefer to use user.app_metadata (rather than user.user_metadata) to store information that’s not intended for the user, themselves, to modify. You can read about this more in our Actions Triggers: post-login - Event Object docs where event.user is discussed.

  3. Avoid adding any custom claim based on user.app_metadata en masse. It’s highly unlikely to be the intent for any new attribute added to app_metadata that may be added in the future to automatically be exposed as a custom claim! Prefer instead to create one, or more, custom claim(s), each based on a specific app_metadata attribute.

We’ve covered a number of different things in this thread. Consequently, somewhat meandering from the original topic of discussion. Generally speaking, to facilitate more focused and clear conversation I’m going to recommend we now let this thread close; please feel free to reach out here in Community for additional help, and myself and the team will be happy to provide you with specific feedback and advice on a topic by topic basis :hugs:


Thank you, @peter.fernandez. Nailed it. Give yourself a biscuit. Thanks again! (And yes, apologies for the meandering thread!)

1 Like

:joy: I’ll take a Chocolate Digestive! Thank you :sunglasses: So glad to hear you got it working; all the best with the rest of your integration :hugs:


This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.