Update Nov 2021 - This content is not dated. Auth0 has a simple way of dealing with the JWTs roles and scopes.
TODO - update this
I have a MEAN stack project that uses an Express/Mongoose API to access a MongoDB datastore. For authorization to endpoints, I used Auth0 and its recommended approach of Scopes which are created, stored, and assigned by API in my Auth0 account. I used Postman for testing and had several collections to test the API endpoints. I documented my work at Roch with a focus on what should be tested and error reporting.
On a recent revisit to my tests, I found several failed after I modified a setting in Auth0. This led to a review of documentation on scopes, roles, and permissions contained in the two most cited standards OAuth 2.0 and OpenID Connect. Beyond the standards, I have not found a recognized authority that explains OAuth 2.0 or OpenID Connect scopes and permissions 1 .
The intent of this note is to document my findings and show the approach that I used to get Auth0 roles and permissions testable and working with my API server 2 . Since I expect that most are interested in the latter, I will start with it.
Implementing Auth0 Permissions
Auth0 API
When authenticated by Auth0 scopes and/or permissions are passed to user in the JWT access-token. The Auth0 API “settings” for your authorized applications allows you to configure the use of Scopes or Permissions. If Role Based Access Control (RBAC) is selected for the API, you can add permissions to your access token (a second selection in Auth0). It is worth checking the JWT access token varying these selections to see the changes. (Use JWT.IO to decode the token.) Note that when RBAC permissions are added to the access token, there is no nested “scopes” object in the JWT access-token.
My API Server
Per Auth0 endpoint documentation, my API server has a sample endpoint:
router.route('/myMatch').post(jwtCheck, jwtAuthz(['create:match']), matchController.postMatch)
When scopes were used, this worked fine, but jwtCheck ignored permissions. The following change corrected the issue and allowed jwtCheck to access the nested permissons in the access-token.
const options = { customScopeKey: 'permissions' };
router.route('/matches').post(jwtCheck, jwtAuthz(['create:match'], options), matchController.postMatch)
My Angular Client
My client-side Angular app relied on Scopes for route guards and sending authenticated requests to the API server. Now that Scopes were not present, the app could only access the home screen. I could not find an "options" parameter to make permissions into scopes. This led to decoding the access-token and I used jwt-decode
to access the nested permissions object. The permissions were converted to an array of Scopes that my app expected. The scope array was stored in localStorage (more on this later). My code is:
const perms = jwt_decode(localStorage.getItem("access_token"));
localStorage.removeItem("scopes");
var scopes: string = "openid profile";
for (var p of perms.permissions) {
scopes = scopes + " " + p; }
localStorage.setItem("scopes", JSON.stringify(scopes));
Scopes, Roles and Permissions
I find the Auth0 documentation confusing in describing the difference between Scopes and Permissions. My puzzlement is likely due to lack of experience and not needing all the features of federated logons and OpenID. I do not live in the world of authentication and authorization, and that is why I chose Auth0; I did not want to become an expert.
As mentioned, OAuth 2.0 and OpenID Connect are the current standards for authorization and authentication. These standards create the ability for a consumer to sign into an app with multiple identities e.g. Facebook, GitHub and a local identity. The standards also create the ability to use a single sign-on across apps.
I reviewed the OAuth 2.0 documentation and found little on permissions. It focuses on an app’s scopes and a user’s claims. I also reviewed Azure Active Directory B2C, a competitive product to Auth0 that follows the OAuth 2.0 standard. It uses a different terminology of claims, user flows, and custom policies.
m3n7alsnak3 on Stack Overflow wrote that “Scopes are per Client app, while permissions are per user. In other words - one client app can have a scope(s) to access certain API(s), but the users of this client app will have different permissions in this api (based on their roles).” It makes sense to me for the client or API to expect “Scopes” or “Permissions from a user, but why make the distinction?
For now, I am defining each role in Auth0 to have a specific list of permissions. The client app extracts the permissions and creates a scope string containing all the expected Scopes. Reader suggestions or clarification would be appreciated.
Local Storage
Local storage aka browser storage was Auth0’s suggested datastore for SPA access tokens and decoded attributes. Its rationale was that the app needed a secret key that couldn’t be used by another app or person. Further, scopes, roles, permissions, … that would be stored in local storage are not unique. A scope of "read:members" or a role of "admin" is hardly unique to my app and not intended to be a sensitive passcode/secret credential.
More recently, the general recommendation is to avoid local storage under certain scenarios due to cross site scripting (XSS) vulnerabilities. Auth0’s recommendation is to use of OAuth’s Authorization Code Flow with Proof Key for Code Exchange (PKCE).
I will put this on my TODO list.
1: This article published in 2017 does a good job of describing OpenID concepts https://developer.okta.com/blog/2017/07/25/oidc-primer-part-1
2: I will update this post with any comments or sources that help to clarify this issue.
Top comments (0)