I’ve read several places that the it should be avoided reading an Access Token when the “reader” isn’t the targeted audience. Which makes sense.
I want my frontend to hide /disable UI (for improved user experience) that isn’t going to work with whatever Access Token the user was given for the backend API. No need to click a button that would give an “Access denied” error anyway.
Should I just let the frontend read the Access Token for the backend API or is there some smart or recommended way to go around this use case?
Reading the access token by anyone other than the intended audience is a security red flag. You can do it, but you shouldn’t.
There is a separation of concerns issue here: the API enforces permissions and the UX displays appropriate options. They are related, but different. If I can make a rough analogy, the API expects permissions, and the UX needs the role (a set of permissions).
My suggestion: put the “Role” or the kind of user in the ID token for your UX to process and decide what is appropriate. Keep the access token opaque w/r/t the front end.
I don’t think I get your analogy, what do you mean by “The UX needs the role”.
It seems to me that I would need to duplicate the meaning of a role in the frontend. Let us imagine that the role Manager has been setup in Auth0 to be allowed to product:create and product:delete. If I only get the Manager role from id token I would need a way to get from the role back two permissions: product:create and product:delete (so I know if I am to show or hide the “Create product” button and “Delete product” button)… or what am I missing?
Also if the Access Token is opaque (without that API audience and therefore without permissions), then I don’t get what token I’m supposed to send to the backend? I thought the backend was supposed to be supplied an Access Token.
Sorry for the confusion about “opaque”. I am not referring to an opaque access token, I meant the front end should treat the token as opaque. You MUST have the audience of course.
After reading your response, I think you are right: the role/permissions are probably too entwined to make that a clean design.
I think the more clean/safe approach would be to extend your API with an endpoing /capabilities that returns the capabilities. This is more complex, but isolates the permissions to Auth0 and the API, and defines a separate concept: “enabled actions” betwee the API and the front end.
On the surface, this may seem to be overkill (and for a first version it may be), but I think it is valid. An “enabled action” may require multiple permissions, not just one, and so there is not a direct 1-1 correspondence between actions a user can perform and permissions. Ignoring this gives a simpler design (perhaps) in the initial case, but as it gets more complex you add logic to the front end about this relationship. Better to encapsulate that in the API.
I think that argument also applies to reading the access token in the front end.
Again, thank you for the interesting question and discussion.
I agree that there might not be a direct 1-1 correspondence between actions a user can perform and permissions. I.e. it would not make sense to show a “Delete” button if the users aren’t allowed to pull a list of products… usually one would place such a button next to the product in the list.
I find “capabilities” and “permissions” to be closely related regardless. Would you be able to elaborate a bit more on the “capabilities” thingie? I find you suggestion very interesting but also a bit vague with my limited knowledge.
Would “capabilities” and “permissions” be related in any way outside my head or a spreadsheet or the like, that I would need to keep updated on the side? Where do you recon the responsibility for this coupling should reside?
You have definitely given me something to think about and I’m sure I will need to let the information sink in for background processing.
If I end up (temporarily) peeking inside the access token (because I don’t have what it takes to figure “capabilities”), I promise that I will not store the access token in the browser in any way (neither cookies, LocalStore or otherwise)
Without knowing too much about your use case, I’d suggest a clean workaround for reading attributes used by the UI only, is indeed to avoid the frontend trying to predict or know anything about the access token. Instead, use Actions to inject the necessary information into the ID token. This is an alternative to John’s idea that doesn’t require an additional API endpoint to essentially read the Access Token on behalf of the frontend. It takes very few lines of code to add an additional claim to your ID token, which your UI is free to read and interpret. You can make this a binding “contract” between the token issuer (Auth0) and your frontend that doesn’t require the frontend or backend to attempt to interpret the Access Token in terms only applicable to the frontend. The Access Token remains opaque, the API can remain ignorant of the UI’s requirements, for true separation of interests. Use your Actions to create this “glue contract” and have that maintained in only one place.
Thanks for your input. I’ve been playing a bit with both Rules and Hooks before, and from what I can gather Actions aren’t too different. It is definitely seems like a viable alternative.
Johns suggestion is clearer to me now, after having munched on it for a few days, I still cannot make up an example myself, where “capabilities” wouldn’t just be replicating the permissions from the Access Token.
It is nice to have two ways of telling the frontend about “capabilities”:
Expose “capabilities” through an API (that the Access Token gives access to)
Expose “capabilities” in the ID Token (e.g. with Actions)
My application is using a WebSocket to connect to the backend API, which means I am not using a good old school HTTP REST API. Instead specific messages over the WebSocket represents the “API” operations.
The frontend authorizes the “session” (WebSocket connection) by sending an Access Token. My backend is notifying the frontend every time the “session” has been successfully authorize (or re-authorized). If the session isn’t authorized (or re-authorized) the connection will be closed by the backend. I was thinking that a solution for me could be including “capabilities” (permissions) in this “authorized successfully” message.
The “capabilities” will never give the frontend any power, it should be seen as information about what can be expected to be allowed/available.
@jacob3 You’ve written a succinct summary of the options. I think either approach could be considered philosophically correct, regardless of whether it’s a Restful or persistent. As to which way you split the hair, I would consider who it is you want to be the authority with regard to the UI’s capabilities, which comes down to the letters in “IAM” - obviously Auth0 is the authority for identity, but it’s up to you how far you want to go with the Access Management. It appears you’re already using access tokens for determining a user’s capabilities with regards to API functionality, which is perfect. If the API “enriches” this information on behalf of the UI, you are (philosophically) distributing some control of the Access Management to the API (or perhaps, just the interpretations). However, this may conflate your API a bit, when you could alternately choose to always derive authority (or interpretations of authority) within Auth0 itself.
It sounds like it’s pretty moot in your case, as the same information is used to control access in the API as would be used to change the UI. But let’s say this became less of a direct relationship at some point in the future. Then you might be glad to have built that control in to Auth0, so you only have to change things in one place, versus modify and redeploy your API. Anyway, just food for thought. By the way, the code to inject e.g. custom claims for certain users into either token is quite straightforward. While the approach is similar between either Rules or Actions, Actions is the more future-facing method, while Rules are being deprecated. Action-oriented example (post-login Action):