Invalid access token returned when using authorication_code grant with the management api

I am trying to use the authorization code flow with an app authorized with the management API. For testing purposes I am doing this using curl and my web browser.

Firsly, I visit the browser using the following URL constructed:
https://<tenant domain>/authorize?audience=https%3A//<tenant domain>/api/v2/&response_type=code&client_id=<client id>&redirect_uri=http%3A//localhost%3A31313

I authorize the application, and get a redirect back to the following url:
http://localhost:31313/?code=<code>

All as expected so far! I then perform the token exchange via curl from the command line:

$ curl -X POST "https://<tenant domain>/oauth/token" --data-urlencode "client_id=<client ID>" --data-urlencode "client_secret=<client secret>" --data-urlencode "grant_type=authorization_code" --data-urlencode "code=<code copied from the above url>" --data-urlencode "redirect_uri=http://localhost:31313" -i

HTTP/2 200
date: Thu, 30 Jan 2025 12:27:37 GMT
content-type: application/json
--- other headers ---

{"access_token":"<access_token>","expires_in":86400,"token_type":"Bearer"}

As you can see everything works just fine! However, the access token does not work when trying to use with the management API:

$ curl "https://<tenant domain>/api/v2/users/<my user id>" -H 'Authorization: Bearer <access token>' -i
HTTP/2 401
date: Thu, 30 Jan 2025 12:43:18 GMT
--- other headers ---

{"statusCode":401,"error":"Unauthorized","message":"Invalid token","attributes":{"error":"Invalid token"}}

Inspecting the token up close I noticed that the middle part of the jwt actually seems to contain invalid json; i.e. if I take the middle bit and base64 decode it I get the following json:

$ echo '<access token>' | awk -F '.' '{print $2}' | base64 -d

{"<custom claim from a rule>":"<redacted>","<custom claim from a rule>":"<redacted>","iss":"https://<auth0 tenant domain>/","sub":"<my user ID>","aud":"https://<auth0 tenant domain>/api/v2/","iat":1738239941,"exp":1738326341,"azp":"<client id>

As you can see it seems correct, except at the end it does not have the closing quote and the closing curly brace. When pasting the full token into jwt.io it says that the signature can be verified, so it doesn’t seem like I’m truncating the access token in any way. What next steps can I take to diagnose this issue further?

Thanks in advance!

Hi @domas,

Welcome to the Auth0 Community!

First, I noticed that your /authorize request is missing the scopes you need to call the Management API successfully. Depending on the endpoint you are calling, you must specify those required scopes.

Particularly for the /api/v2/users/{id} endpoint, you will need to include the
read:current_user scope. (Reference: Auth0 Management API v2)

Then I suggest decoding your access token in jwt.io and ensuring that your payload looks similar to the following with the correctly referenced audience and has the required scopes:

{
  "iss": "{AUTH0_DOMAIN}",
  "sub": "auth0|0123456789",
  "aud": [
    "{AUTH0_DOMAIN}/api/v2/",
    "{AUTH0_DOMAIN}/userinfo"
  ],
  "iat": 1738275401,
  "exp": 1738361801,
  "scope": "openid profile email read:current_user",
  "azp": "{clientID}"
}

Thanks,
Rueben

I should have specified in the original post, but I actually have tried with the scopes as well. I just tried one more time to make sure and I’m having the same problem.

I used the following URL this time:

https://<tenant domain>/authorize?audience=https%3A//<tenant domain>/api/v2/&scopes=openid%20profile%20email%20read%3Acurrent_user&response_type=code&client_id=<client id>&redirect_uri=http%3A//localhost%3A31313

The token exchange I performed the same as in the original post, and the resulting token, when decoded manually looks like this (formatted for clarity)

{
  "< custom claim >":"< custom claim value >",
  "< another custom claim > ":"<custom claim value>",
  "iss":"https://<tenant domain>/",
  "sub":"<my user id>",
  "aud":"https://<tenant domain>/api/v2/",
  "iat":1738345358,
  "exp":1738431758,
  "azp":"<client id>

Once again - after azp there’s no closing quote and no closing bracket.

When pasted into jwt.io The payload is reported as follows:

{
  "< custom claim >": " < custom claim value >",
  "<another custom claim >": "< custom claim value >",
  "iss": "https://<tenant domain>/",
  "sub": "<my user id>",
  "aud": "https://<tenant domain>/api/v2/",
  "iat": 1738345358,
  "exp": 1738431758,
  "azp": "<client id>"
}

As you can see in jwt.io the closing quote and the closing brace are reported, but like I mentioned - I use command line tools to decode it (echo ‘’ | awk -F ‘.’ ‘{ print $2 }’ | base64 -d`) and I’m not sure how I could possibly truncate the access token such that it would result in this.

Additionally the payload from jwt.io and from manual decoding does not seem to contain the fields you suggest should be present.

Hi @domas,

It looks like your decoded does not have the scope claim even though you specified it in the /authorize request.

I followed the same command line curl requests to exchange for a token and managed to get and decode the access token without issues.

In my tests, calling the command echo '{MY_ACCESS_TOKEN}' | awk -F '.' '{print $2}' | base64 -d correctly decodes the access token ending with the closing quote and closing curly brace.

Additionally, the decoded token I received contained the same claims as mentioned in my previous reply, including 2 audiences and the passed-in scopes. Specifically, one references the /userinfo endpoint, the other references the /api/v2/ endpoint, and the openid, profile, email, and read:current_user scopes.

At this moment, I believe the issue might be caused by the way you are decoding the token. One thing that stands out is that the decoded token from jwt.io decodes correctly, but running your command truncates it. I’d recommend looking into this further to see if your access token in your terminal is being truncated.

Let me know what you discover.

Thanks,
Rueben

Is there any reason this could be the case? The decoding issues aside, I do not get this in jwt.io either. Is there any configuration I could have missed that would lead to this?

Hi @domas,

The only possibility for causing the scope claim not to be included is if the scope claim was not specified in the /authorize request.

For example:

https://{yourDomain}/authorize?
    response_type=code&
    client_id={yourClientId}&
    redirect_uri={https://yourApp/callback}&
    audience={apiAudience}&
    state={state}

I have tested and confirmed that this behavior happens only when omitting the scope query parameter.

As an additional test, you could try the /authorize endpoint on a browser and check the network activity to ensure the scope query parameter was passed in.

Then, use the terminal to exchange your code for an access token. This way, you can at least deduce whether there’s an issue with the CURL requests you made to the /authorize endpoint.

Thanks,
Rueben

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