Search code examples
spring-bootoauth-2.0spring-authorization-server

Allow scopes based on user role


I'm using a Spring Authorization server with two user roles: employee and admin. Employees are limited to the read scope, while admins have access to both read and write scopes. Both types of users access the same single-page application, which serves as an OAuth2 client.

Is it practical to implement this setup? Would it be better to have separate clients for employees and admins, or is there another preferable approach?

To manage this, I've customized the token to verify the user's permitted scopes against the database. Any unauthorized scopes are removed for the respective user role before being included in the token:

@Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer(ScopeService scopeService, RoleService roleService) {
        return context -> {
            Set<String> requstedScopes = context.getAuthorizedScopes();
            Collection<? extends GrantedAuthority> authorities = context.getPrincipal().getAuthorities();

            Collection<Role> roles = roleService.mapAuthoritiesToRoles(authorities);

            Set<String> allowedScopes = scopeService.getAllAllowedScopes(roles).stream().map(Scope::getName).collect(Collectors.toSet());

            Set<String> grantedScopes = requstedScopes.stream().filter(requstedScope -> allowedScopes.contains(requstedScope)).collect(Collectors.toSet());

            JwsHeader.Builder headers = context.getJwsHeader();
            JwtClaimsSet.Builder claims = context.getClaims();
            if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
                claims.claims((c) -> {
                    c.put("scope", grantedScopes);
                });

            }
        };
    }

Although the JWT token now has the correct scopes, the HTTP response body still includes all scopes. How can I exclude certain scopes from the response body?


Solution

  • Scopes are fixed at design time and the same for all users who use a client. One option to enable different scopes across users is to have multiple clients, eg. one for admins and another for employees.

    A more powerful and dynamic option is to issue your own claims to access token. These are assigned different values per user at runtime.

    So in your case, issue the role value as a claim, and anything else useful for authorization, eg. access_level: readonly. Resource servers will then use both scopes and claims together for their authorization when they receive access tokens.

    Consider designing scopes to represent high level access to a business area, eg products or trades or whatever makes sense for your use case.

    When different users of the same client have different privileges, scopes of read and write do not scale very well. Use claims to avoid needing to use scopes in unnatural ways. This will also keep the number of scopes manageable and avoid scope explosion.