Auth0 Home Blog Docs

JWT with roles for Spring Security

jwt
roles
spring

#1

I’m trying to setup Auth0 with an existing API using spring security. This API works with a single page app front end.
From what I understand I need to use JWT, and add custom claims that will contain my roles inside the JWT.

In order to retrieve the JWT I downloaded the javascript quickstart and modified it slightly to retrieve the access token and provide an audience and the roles scope. This is the content of my app.js:

window.addEventListener('load', function() {

    var lock = new Auth0Lock(AUTH0_CLIENT_ID, AUTH0_DOMAIN, {
    auth: {
    responseType: 'token',
    params: {
        audience: 'my audience',
        scope: 'openid roles'
      }}
  });

  // buttons
  var btn_login = document.getElementById('btn-login');
  var btn_logout = document.getElementById('btn-logout');

  btn_login.addEventListener('click', function() {
    lock.show();
  });

  btn_logout.addEventListener('click', function() {
    logout();
  });

  lock.on("authenticated", function(authResult) {
  console.log(authResult.accessToken)
    lock.getProfile(authResult.idToken, function(error, profile) {
      if (error) {
        // Handle error
        return;
      }
      localStorage.setItem('id_token', authResult.idToken);
      // Display user information
      show_profile_info(profile);
    });
  });

  //retrieve the profile:
  var retrieve_profile = function() {
    var id_token = localStorage.getItem('id_token');
    if (id_token) {
      lock.getProfile(id_token, function (err, profile) {
        if (err) {
          return alert('There was an error getting the profile: ' + err.message);
        }
        // Display user information
        show_profile_info(profile);
      });
    }
  };

  var show_profile_info = function(profile) {
    var avatar = document.getElementById('avatar');
    document.getElementById('nickname').textContent = profile.nickname;
    btn_login.style.display = "none";
    avatar.src = profile.picture;
    avatar.style.display = "block";
    btn_logout.style.display = "block";
  };

  var logout = function() {
    localStorage.removeItem('id_token');
    window.location.href = "/";
  };

  retrieve_profile();
});

Using this app I’m able to login properly, but the token that I’m getting doesn’t reflect the roles of the user. (the roles are supposed to be added by a rule)

I’m getting a JWT that has the following:

{
  "iss": "https://blabla.auth0.com/",
  "sub": "auth0|blabla",
  "aud": 
    "my audience",
    "https://blabla.auth0.com/userinfo"
  ],
  "azp": "blabla",
  "exp": 1495240761,
  "iat": 1495233561,
  "scope": "openid roles"
}

I’ve read that it might be because of the OICD specification etc, but not too sure.

Would any of you have recommendations on how I could get the proper JWT to access my API’s endpoints?


Update (follow-up question):

I’m now able to see the roles in my access token:

  {
      "iss": "https://blabla.auth0.com/",
      "sub": "auth0|blabla",
      "aud": 
        "https://api.example.com",
        "https://blabla.auth0.com/userinfo"
      ],
      "azp": "something",
      "exp": 1495484775,
      "iat": 1495477575,
      "scope": "openid roles email picture",
      "https://api.example.com/roles": 
        "ROLE_ADMIN"
      ]
    }

However it seems like Spring Security is not able to read those roles.
This is my security config:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        JwtWebSecurityConfigurer
                .forRS256("https://api.example.com", "https://blabla.auth0.com/")
                         .configure(http)
            .headers()
                .addHeaderWriter(new CacheControlHeadersWriter())
                .addHeaderWriter(new XContentTypeOptionsHeaderWriter())
                .addHeaderWriter(new XXssProtectionHeaderWriter())
            .and()
                .addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class)
            .csrf()
                .disable()
            .authorizeRequests()
                .antMatchers("/v1/login/**").hasAnyRole("ROLE_ADMIN")
            .and()
                .httpBasic();
    }

It seems like it’s able to authenticate me, but then I get a 403 status code.

I tried to change the rule to change the roles field syntax so it looks like this: "https://api.example.com/roles": "'ROLE_ADMIN']"
but still the same issue.

Here is the code for my rule:

    function (user, context, callback) {
      var namespace = 'https://api.example.com/';
      context.accessToken[namespace + 'roles'] = 'ROLE_ADMIN'];
      callback(null, user, context);
    }

Any idea?


#2

If you’re already obtaining an access token that lists your API as one of the audiences then the likely cause for it to not include the information you expect is the rule implementation. More specifically, the correct way to add custom claims to the issued access token is to do something similar to;

function (user, context, callback) {
    const namespace = 'https://api.example.com/';

    context.accessToken[namespace + 'roles'] = "[your_roles_here]";

    callback(null, user, context);
}

The important parts are that you use context.accessToken to set your custom claims and that those same custom claims need to be namespaced (the namespace part is so that we ensure that there are no overlap between custom claims and OIDC claims; even future ones).

For reference information related to setting custom claims in issued tokens see:

https://auth0.com/docs/api-auth/tutorials/adoption/scope-custom-claims#custom-claims


Update:

It seems that JwtWebSecurityConfigurer will look at the scope claim to fill the authorities associated with the authenticated principal. Given that hasRole checks the authorities, but the actual role data is contained within a claim that was not used as source for the authorities then it fails.

The use of that library implies that the authorities will have to be available at the scope claim of the access token and then you can use hasAuthority to check for it. Note that this is my interpretation of how that library works; I might have missed some way to configure this, but I don’t think so as this issue suggests it needs to be the scope claim.


#3

Hello. Would someone show me the code please? I have some questions and would like to take a look at the code to solve my doubts. Thanks!


#4