spring-bootjwtoktaclaims

How to convert spring boot app with Okta to read okta groups


A while back, I created a spring book application that uses Okta as the OIDC provider. It's based on a previous version of this great example by Matt Raible.

What I'm trying to do now is obtain the group or roles for the logged in user.

Matt responded to Spring Boot / Okta - how to retrieve the users groups saying I should add claims to Okta interface, which I've done.

He qualified it, saying

Then, if you're using the Okta Spring Boot starter, your groups will automatically be converted to Spring Security authorities.

I converted to Spring Boot starter kit, with the following steps:

  1. Added the starter kit to pom.xml

  2. changed application properties to use variables from the starter kit as follows:

    #okta spring boot starter kit
    
    okta.oauth2.issuer=https://dev-xxxx.okta.com/oauth2/default
    
    okta.oauth2.audience=api://default
    
    okta.oauth2.groupsClaim=groups
    
    #okta spring boot starter kit test
    
    okta.oauth2.clientId=xxxxxxxxxxxx
    
    okta.oauth2.clientSecret=xxxxxxxxxxxxxxxxx
    

And it works as it did before.

However, when I print the Authorities, I still don't see the groups. The only authority I see is this:

Authority: ROLE_USER Username: xxxxx@gmail.com

Heres the code which prints the authorities:

Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();
for (GrantedAuthority authority : authorities) {
    System.out.println("Authority: " + authority.getAuthority());
}

Is there anything I'm doing wrong here?

Here's an image of the claims: Claims


Solution

  • I thought that adding the groupsClaim property would automatically map values to authorities. Are you sure you're adding it to the ID token?

    The following technique is what we use in JHipster, which uses Spring Security w/o the Okta starter.

    Add a GrantedAuthoritiesMapper bean in your security configuration.

    /**
     * Map authorities from "groups" or "roles" claim in ID Token.
     *
     * @return a {@link GrantedAuthoritiesMapper} that maps groups from
     * the IdP to Spring Security Authorities.
     */
    @Bean
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return (authorities) -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
    
            authorities.forEach(authority -> {
                // Check for OidcUserAuthority because Spring Security 5.2 returns
                // each scope as a GrantedAuthority, which we don't care about.
                if (authority instanceof OidcUserAuthority) {
                    OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
                    mappedAuthorities.addAll(SecurityUtils.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims()));
                }
            });
            return mappedAuthorities;
        };
    }
    

    The relevant methods from SecurityUtils are:

    public static List<GrantedAuthority> extractAuthorityFromClaims(Map<String, Object> claims) {
        return mapRolesToGrantedAuthorities(getRolesFromClaims(claims));
    }
    
    @SuppressWarnings("unchecked")
    private static Collection<String> getRolesFromClaims(Map<String, Object> claims) {
        return (Collection<String>) claims.getOrDefault("groups",
            claims.getOrDefault("roles",
            claims.getOrDefault(CLAIMS_NAMESPACE + "roles", new ArrayList<>())));
    }
    
    private static List<GrantedAuthority> mapRolesToGrantedAuthorities(Collection<String> roles) {
        return roles.stream()
            .filter(role -> role.startsWith("ROLE_"))
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());
    }