How to represent multiple access level in an application calling an API?

I use Spring Boot with Spring Security. I have three user types:

  • admin;
  • manager;
  • user.

How can I protect endpoint for particular type of user like admin? I found that rules can be useful but is there an option to add to body response (which contains access_token) a custom field like role? Is there solution to differ these users?

I followed this tutorial and I configured my environment in similar fashion:

https://auth0.com/docs/quickstart/backend/java-spring-security/01-authorization

If you’re looking for the approach that would have more built-in support then you should use scopes as the way to represent the requirements of each endpoint which would then allow you to use the integrated support in the library you configured for doing something like this:

.antMatchers(HttpMethod.PUT, "/photos/**").hasAuthority("update:photos")

The above means that a PUT request to all photo related endpoints require that the access token provided as means of authorization contains the scope update:photos. That would be the only thing you would need to on the API side.

In addition, you would have to implement an authorization policy that translates the notion of user type into the appropriate set of scopes which would then be included in the issued access token. At this time, the available mechanism to implement such translation/mapping is to do it in a custom rule.

For example, take in consideration the following rule that dictates that the user with an email of user1@example.com only has access to the read scope. In this sample rule the authorization policy is done based on the email and is pretty much hardcoded, however, you can adapt it to be based on something else than email (your user type) and you can even externalize that logic and replace the body of the restrictScopes function with a call to an external system. The important part is doing a similar set of steps that restricts the scopes being asked by the client application to the ones the end-user actually has permission for.

function (user, context, callback) {
   var _ = require("lodash");

   var req = context.request;

   // Get requested scopes and audience
   var scopes = (req.query && req.query.scope) || (req.body && req.body.scope);
   var audience = (req.query && req.query.audience) || (req.body && req.body.audience);

   // If you have configured a default audience uncomment and
   // update the following line
   //// audience = audience || "[account-default-audience-identifier]";

   // Bail out if this is not an API authorization request
   if (!audience) return callback(null, user, context);

   // Normalize scopes into an array
   scopes = (scopes && scopes.split(" ")) || ];

   // Restrict the access token scopes according to the current user
   context.accessToken.scope = restrictScopes(user, audience, scopes);

   callback(null, user, context);

   function restrictScopes(user, audience, requested) {
     // For illustration purposes we assume we only have one audience
     // so the below code does not check the specified audience parameter

     // Full list of API scopes available hardcoded for demo purposes
     var all = "read:examples", "write:examples"];

     // Full list of OIDC scopes that can also be used during authorization
     var oidc = "openid", "profile", "email", "phone", "address"];

     // Applies hardcoded logic to restrict the possible scopes;
     // replace with your access control logic that can perform
     // external requests or use data available at the user level
     var allowed;
     if (user.email === "user1@example.com") {
       allowed = "read:examples"];
     } else {
       allowed = all;
     }

     // IMPORTANT: Have in mind that in this simplified example it is assumed
     // that all the API's that can be requested support offline access so the 
     // offline_access scope is always added to the allowed list
     // If in your scenario requested audiences have different configurations
     // regarding the allowance of offline access then you need to add the
     // scope conditionally based on the requested audience
     allowed.push("offline_access");

     // Always allow all OIDC scopes if they are requested; this
     // assumes that the requested audience uses RS256 which allows
     // the access token to be used against /userinfo if OIDC scopes
     // are also requested. For HS256 API's this could be removed as
     // it would not have any advantage in including OIDC scopes
     allowed = _.union(allowed, oidc);

     // Intersect allowed with requested to allow the client
     // application to request less scopes than all the ones the
     // user has actually access to. For example, the client
     // application may only want read access even though the
     // user has write access
     return _.intersection(allowed, requested);
   }
}

In addition, have in mind that as soon as an access token is issued it would be self-contained so if the access token has a lifetime of ten hours and the user permission set changes immediately after an access token is issued then the access token would not reflect the actual user permission set in real time; depending on your scenario this may or may not be an issue, but I wanted to call your attention to this.