Customize OAuth 2.0 access_token to add role access with Azure AD

In my previous post, we saw how we can obtain access_token and access protected REST resources using OAuth 2.0 with Azure Active Directory. Now we will have a look at how we can customize token so it can have role based permissions. Only authorized user with allowed role can access a given resource.

Let’s provide Admin access to our protected user resource we have created in previous post using annotation @PreAuthorize(“hasRole(‘ROLE_ADMIN’)”) as folllows.

@RestController
@RequestMapping("/user")
public class UserResource {

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/email")
    public String email(){
        OAuth2Authentication authentication = (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();;
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                (UsernamePasswordAuthenticationToken) authentication.getUserAuthentication();
        return (String)((Map) usernamePasswordAuthenticationToken.getDetails())
                .getOrDefault("userPrincipalName","Result Not Found");
    }

}

In above snippet REST resource is annotated with @PreAuthorize now if a client invoke resource /user/email with the access_token obtained in previous post it will result Access Denied with status 403 to client as follows:

Since resource /user/email can be accessed by a user who has admin role therefor access_token has to be given admin role on some condition that satisfy logged in user’s role. Here we will simply add admin role so that given resource can provide result to client.

Following Java class is extension of AbstractAuthenticationToken and allow addition of roles to Authentication Principle

public class CustomOAuth2Authentication extends AbstractAuthenticationToken {

    private final OAuth2Request storedRequest;
    private final Authentication userAuthentication;

    public CustomOAuth2Authentication(OAuth2Authentication authentication, Set<String> roles) {
        super(authentication.isClientOnly()
                ? authentication.getAuthorities()
                : filter(authentication.getAuthorities(), authentication.getOAuth2Request().getScope(), roles));
        this.storedRequest = authentication.getOAuth2Request();
        this.userAuthentication = authentication.getUserAuthentication();
        setDetails(authentication.getDetails());
        setAuthenticated(authentication.isAuthenticated());
    }

    /**
     * Retains only the authorities from the set of approved scopes and add new authorities mapped.
     */
    private static Collection<GrantedAuthority> filter(Collection<? extends GrantedAuthority> authorities, Set<String> scope, Set<String> newRoles) {
        final List<GrantedAuthority> result = new ArrayList<>();
        result.addAll(
                authorities.stream().
                        filter(authority -> scope.contains(authority.getAuthority())).
                        collect(Collectors.toList()));
        result.addAll(
                newRoles.stream().
                        map(role -> new SimpleGrantedAuthority(role)).
                        collect(Collectors.toList()));
        return result;
    }

    @Override
    public Object getCredentials() {
        return userAuthentication.getCredentials();
    }

    @Override
    public Object getPrincipal() {
        return userAuthentication.getPrincipal();
    }

    public OAuth2Request getStoredRequest() {
        return storedRequest;
    }

    public Authentication getUserAuthentication() {
        return userAuthentication;
    }


}

Now Add following methods in class OAuth2Config we have created in previous post.

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.resourceId("resource");
    resources.authenticationManager(authenticationManager);
}

private OAuth2AuthenticationManager authenticationManager = new OAuth2AuthenticationManager() {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) super.authenticate(authentication);
        Set<String> roles = new HashSet<String>(){{
            add("ROLE_ADMIN");
            add("ROLE_USER");
        }
        };
        return new CustomOAuth2Authentication(oAuth2Authentication, roles);
    }
};

Above snippet creates a custom Authentication instance enhanced with set of roles for a any authentication request.
Since Authentication object is customized to CustomOAuth2Authentication from OAuth2Authentication so Authentication has to cast to CustomOAuth2Authentication

@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/email")
public String email(){
    CustomOAuth2Authentication authentication = (CustomOAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();;
    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
            (UsernamePasswordAuthenticationToken) authentication.getUserAuthentication();
    return (String)((Map) usernamePasswordAuthenticationToken.getDetails())
            .getOrDefault("userPrincipalName","Result Not Found");
}

Now If you access protected resource it will provide result to the client as access_token is enhanced with ADMIN Role access and obtain result as follows where earlier denied access.

Source code is available here

Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *