Re-issue access token with subset of permissions of original JWT

Hi,

We’re using the JWT access token to manage SPA’s access to its REST API.
The token scopes are used to grant custom permissions, for instance:

{
  "iss": "https://example.auth0.com/",
  "sub": "auth0|user1234567890abcdefghij",
  "aud": [
    "https://api.example.com/",
    "https://example.auth0.com/userinfo"
  ],
  "iat": 1592186168,
  "exp": 1592272568,
  "azp": "AuthorizedPartyClientID123456789",
  "scope": "openid profile email read:builds create:builds update:builds"
}

The protected API endpoint POST /builds triggers a long-running build job.
The system needs to track completion of the build and record its result.
Once the job finishes, it can notify the API endpoint PUT /builds/<build_id>.
It’s desirable to restrict access to this endpoint via Auth0 analogically to other endpoints.
The API does need the user context (user_id) to function.

Can the API server issue a new JWT with a subset of scopes of the original JWT?
The API server does have full access to the Auth0 Management API.
Newly issued token should be valid at the time when the build job finishes.
Thus, the token’s lifetime may need to exceed that of the original JWT.
The desired token claims would be as follows:

{
  "iss": "https://example.auth0.com/",
  "sub": "auth0|user1234567890abcdefghij",
  "aud": [
    "https://api.example.com/"
  ],
  "iat": 1593186168,
  "exp": 1593358968,
  "azp": "AuthorizedPartyClientID123456789",
  "scope": "update:builds"
}

I hope it is possible with Auth0. Otherwise, the endpoint would have to be “public” only protected by custom home-grown mechanisms involving a random secret in a persistent storage. That’s highly undesirable as the persistent storage/database is not in place in this serverless architecture.

Hi @sshymko,

Welcome to the Community!

Typically, in this situation you would register the API server as a Machine to Machine Application, and attach the user ID to the request outside of the token.

Doing this you can restrict the permissions the app can request, and limit the token’s scope.

Would that work?
Dan

Thanks for your reply.

The described scenario is only part of what the API server does. It does utilize full access to the Management API for the needs of other endpoints.

Yes, the user_id can be passed in a plain query string parameter. The point is an additional persistent storage/database would still be required to protect this endpoint in a different way from all other endpoints using Auth0 JWT. That is very unfortunate.

1 Like

Let us know if you have any other questions.

The Client Credentials Grant does provide an acceptable solution. It allows an API server to issue a JWT access token with any desired scopes (permitted in the application settings) using the machine-to-machine application credentials.

The only issue with a JWT issued in this way is the sub claim no longer contains the user_id context. The solution is to add the custom claim https://example.com/user_id via the Client Credentials Exchange Hook and update the API server to use it instead of sub. The user_id field would need to be passed in the OAuth authentication payload.

The hook implementation (Node.js):

module.exports = function(client, scope, audience, context, cb) {
  const namespace = 'https://example.io/';
  let access_token = {};
  access_token.scope = scope;
  if (context.body.user_id) {
    access_token[namespace + 'user_id'] = context.body.user_id;
  }
  cb(null, access_token);
};

The client implementation (PHP):

$auth = new \Auth0\SDK\API\Authentication(
    'example.auth0.com',
    '<client_id>',
    '<client_secret>',
    'https://api.example.com/'
);
$credentials = $auth->client_credentials([
    'scope' => 'update:builds',
    'user_id' => $userId,
]);
$jwt = $credentials['access_token'];

Decode and verify the JWT claims:

$parser = new \Auth0\SDK\JWTVerifier([
    'authorized_iss'    => ['https://example.auth0.com/'],
    'valid_audiences'   => ['https://api.example.com/'],
    'supported_algs'    => ['RS256'],
]);
$claims = $parser->verifyAndDecode($jwt);
echo json_encode($claims, JSON_PRETTY_PRINT);
{
    "https://example.com/user_id": "auth0|user1234567890abcdefghij",
    "iss": "https://example.auth0.com/",
    "sub": "wMcbzPRjIMTZfApCmuYKO4cAhP809x3N@clients",
    "aud": "https://api.example.com/",
    "iat": 1593459290,
    "exp": 1593545690,
    "azp": "wMcbzPRjIMTZfApCmuYKO4cAhP809x3N",
    "scope": "update:builds",
    "gty": "client-credentials"
}

Hope that helps somebody.

1 Like

Thanks for posting a thorough update!

1 Like