What is your best practice guidance for a Rails API - use an opaque or JWT-based access token?

Hi,

I am aware that both opaque and JWT-based access tokens are supported but I am trying get a sense of your best practice recommendations (eg. if one was to start an SPA+API app tomorrow).

From Tokens, it says:

In your applications, treat access tokens as opaque strings since they are meant for APIs. Your application should not attempt to decode them or expect to receive tokens in a particular format.

  • When it says “applications” is it referring to the JS SPA application only (ie. not the Rails API part of the stack)? For surely the Rails API necessarily must attempt to decode them (assuming they are JWTs in this case)?
  • Or is this line saying that the best practice is that the JS SPA application should send opaque access tokens (instead of JWT access tokens) to the Rails API?

Access tokens must never be used for authentication. Access tokens cannot tell if the user has authenticated.

  • Surely, again, this can only refer to the JS SPA application. As the main point of the access token (certainly if it is a JWT) is to tell the Rails API that the user is authenticated (one has to be authenticated before one can be authorized, correct?). Though, strictly speaking, the ID token models the user (and is what is the JS application uses to “log in” the user), it is reasonable to say that the access token is a representation of what resources in the API that user can access from the Rails API, correct?
  • The big question is - once the Rails API receives a JWT-based access token (and validates the access token and, optionally, calls a user info endpoint) is there anything else it needs to do before it begins to use information in that access token? To me, the wording in the docs above is ambiguous/confusing in this regard.

The last two questions that all this raises overall are:

  • So is there a best practice, from Auth0’s perspective, with respect to whether an access token should be a JWT or an opaque string?
  • If the best practice is to use an opaque string then is there a reason that the Rails API example repo (linked from the Rails API quickstart guide ) uses a JWT instead of an opaque string as an access token?

Lots of questions for a Friday evening :slight_smile: Hopefully, I have explained them clearly.

Have a good weekend,
Declan

P.S. I did read this - Why is my access token not a JWT? (Opaque Token) - Auth0 Community post, which is very good (and explains how to switch between the opaque style and the JWT style of access token), though it doesn’t answer my above questions.

Hi @declan,

Thanks for posting your questions!

The short answer is if your SPA is using your own custom API, then the Access Token you receive from Auth0 during authentication will be a JWT.

When you provide an audience with the authentication request, then Auth0 will provide you with a JWT Access Token so that your custom API can access helpful metadata such as expiration and any custom claims, etc. If you do not provide an audience, then the Access Token consumer is your Auth0 tenant’s /userinfo endpoint. This API happens to use opaque tokens, but this may or may not change.

Here is a good explanation from a similar topic:

  • When it says “applications” is it referring to the JS SPA application only (ie. not the Rails API part of the stack)? For surely the Rails API necessarily must attempt to decode them (assuming they are JWTs in this case)?

Yes, just the SPA Application only. Whether the Access Token is intended to be used to access data from your custom API or your Auth0 tenant’s /userinfo endpoint, the SPA does not need to analyze the token. It will simply send the Access Token with requests as a bearer token.

  • Or is this line saying that the best practice is that the JS SPA application should send opaque access tokens (instead of JWT access tokens) to the Rails API?

It is best practice and follows OIDC protocol to use JWTs for ID tokens, but as far as Access Tokens, it just depends on the contract between the authorization server and the resource server (the API in this example).

It might help to know about the background behind ID Tokens and Access Tokens. OAuth2 is a protocol that serves as an authorization layer that allows users to grant access to applications (the SPA in this case) to use their protected resources (data managed by the API in this example) through an authorization server (Auth0). If following OAuth2 alone, authorization can be granted to the client (the SPA) without the user ever exposing their identity.

OIDC is an identity layer built on top of OAuth that allows applications actually to authenticate. This is where the ID Token comes into play.

When the user authenticates with Auth0, Auth0 will provide an Access Token and an ID Token. The client (the SPA) can validate the ID Token and use it to ensure that the user successfully authenticated with the authorization server (Auth0). Since the ID Token is always a JWT, the client can decode it to get data about the user.

However, as far as the Access Token goes, the client (the SPA) does not care about it since it is between the Resource Server (the API) and the authorization server (Auth0). It can simply pass it along with API requests.

Similarly, the resource server (API) does not have to be concerned with who the user is. It simply needs to ensure that the Access Token aligns with its agreement with the authorization server (Auth0).

Here is a diagram of the OAuth2 Authorization Code flow with PKCE:

  • The big question is - once the Rails API receives a JWT-based access token (and validates the access token and, optionally, calls a user info endpoint) is there anything else it needs to do before it begins to use information in that access token? To me, the wording in the docs above is ambiguous/confusing in this regard.

Validating the token and following the example in the Ruby on Rails QuickStart should be sufficient.

Auth0 issues JWTs when you provide an audience to your custom API. Opaque and JWT Access Tokens follow the protocols. (As long as the ID Token is a JWT)

Auth0 will provide a JWT in this scenario.

Hope that helps answer your questions!

Stephanie

2 Likes

Thanks @stephanie.chamblee - that’s a great answer :+1:

Much appreciated,
Declan

1 Like

Hi @stephanie.chamblee,

No expectation of a reply (though maybe I have gotten some things here horribly wrong :grinning: ). I just wanted to document the thoughts arising from my experiences above for anyone else reading this…

tl;dr - Should User Details Go Into The ID Token, Access Token Or Userinfo Endpoint?

  • The details here should hopefully answer the final point raised in this other Auth0 community forum thread - why is there a need to even use the userInfo endpoint
  • For most developers using access tokens to guard a REST API powering a SPA, you will be expecting an “aud” (audience) field. This is because your API will be likely configured in Auth0 and you will have specified an audience there. Thus, it appears to make no sense to not supply the “aud” field when calling the API. This means that you will be using JWTs and not opaque access tokens.
  • You will have a difficult choice - put any meaningful info about the user into the access token (as custom claims) OR surface this info via the /userinfo endpoint but…
  • In Auth0, any custom claims that go into the /userinfo endpoint are automatically surfaced in the ID Token and this cannot be prevented. This does not appear to be an unusual practice - Microsoft Azure Active Directory seems to follow a similar approach. But it does mean that anything you surface in the /userinfo endpoint will be exposed to the front end in the ID token.
  • This may impact the difficult choice I noted a moment ago. The developer may decide that they might as well put the extra user info into the access token (and consider encrypting those details if they are indeed sensitive). Because if those details go into the /userinfo endpoint, instead of the access token, they will be exposed in the ID token anyway.
  • Alternatively, the developer may decide to keep these details out of the access token and the /userinfo endpoint (and thus the ID token as well). Instead, they may decide to use a proprietary API such as the Auth0 User Management API - however, such a choice means that every API secured in this way needs to make additional calls to the proprietary API and store yet more credentials to do so (more that if they could just have used the /userinfo endpoint - as that merely requires the access token).
  • In summary, this means that if you want to keep user details private to your API and hidden from the front end, you need to (1) encrypt those details and put them in the access token or (2) figure out a non-OIDC (non-Open ID Connect) standard way of getting that information.

Long Version - My Best Guess At Why The Userinfo Endpoint Has Become Less Useful In Contemporary OIDC Architectures?

To give background to my above conclusions, I will outline my broad (possibly misguided) take on why modern OIDC implementations have evolved in this way…

  • I think that a long time ago (in the OIDC community generally) there was more focus on the /userinfo endpoint as being useful.

  • Since then, OIDC solution vendors like Microsoft have come to dislike the /userinfo endpoint because of the extra remote calls and the extra complexity. As this Microsoft Azure Active Directory article says:

Because you can get an ID token at the same time you get a token to call the UserInfo endpoint, we suggest that you use that ID token to get information about the user instead of calling the UserInfo endpoint. Using the ID token will eliminate one to two network requests from your application launch, reducing latency in your application.

  • At the same time, many people still like to claim that putting even basic user details - like the user’s full name - into an access token is a security risk. Instead, use the /userinfo endpoint. There are an endless amount of blog posts dedicated to this for example. (Sidenote: The difficult part for the developer responsible for implementing an OIDC-based solution is deciding whether this is security theatre or whether it represents a genuine risk). But, it would seem that this userinfo mechanism for covertly getting such user details to the server is no longer designed as mechanism to covertly get such user details to the server - because nobody really cares about using the /userinfo endpoint anyway when implementing a typical REST API to power a Single Page Application (SPA). Rather, the broad groupthink across folks using OIDC is that this is in fact security theatre. If you want details in the /userinfo endpoint then you are going to “leak” those details in the ID token. As noted above, Microsoft’s implementation already concedes that. Similarly, Auth0 couples the information in the /userinfo endpoint to the information in the ID Token (which does not seem to be an unreasonable approach given the groupthink I have just noted).

  • Therefore, it seems to me, like any recommendation that says there is merit to “hiding” claims from the access token is misguided, especially for basic details, like a user’s name. If you really want to hide these details you need to
    (1) encrypt them OR
    (2) establish an out-of-band backchannel to your OIDC provider (eg. the user endpoints of Auth0’s Management API ) or another user API that developer would implement themselves (which sounds like a lot of effort and may appear to negate the point of leveraging a service like Auth0 in the first place).

  • The only other possible counter-argument to my last point, that I can think of, is that ID Tokens typically expire very quickly (eg. after 5 mins), whereas an access token can last for (for example) 2 hours. So, the ID token should provide a smaller attack surface than access tokens. That may suggest that there is some merit in putting basic user details into the ID token/userinfo endpoint but not into the access token. However, from a security perspective, it seems hard to justify - the fact that you consider details sensitive enough to omit from the access token would suggest to me that those details are also too sensitive to expose to the Javascript in an SPA application. Arguments could be made that access tokens may pass through more proxy servers, etc but that seems like a weak argument (as https is likely being used and access tokens would be in the request headers so they wouldn’t typically be logged). I would be interested to hear other opinions on this from the community.

All in all, it seems reasonable that a developer would choose to put basic user details into an access token. Maybe I am underestimating the value of the arguably more ephemeral nature of the ID token (though it doesn’t seem very ephemeral to me - as the ID token is commonly sent as an id_token_hint to help fully logout the user in some OIDC implementations). Using a /userinfo endpoint or proprietary user APIs to get basic user details adds complexity (especially the latter as it requires more credentials). For example, if an organisation has 10 APIs, that means that 10 separate codebase locations will need to call a userinfo endpoint (or proprietary user API) and likely implement a caching strategy for responses. This seems like a lot of work that can be mitigated by putting the basic user details in the access token - if an organisation is okay with the resulting tradeoff.

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