Fetch user information in Resource Server

Viewed 109

I have configured the resource server which verify JWT token against auth server. In code bellow you can see my configuration which has defined issuer-uri (is URI from Auth0). If user is authenticated on my public client against Auth0, this client receive JWT token from Auth0. When I call resource server with token header, user is authorized, and resources are available, but SecurityContextHolder contains only base data parsed from JWT, and not whole information about user. I have available userinfo endpoint from Auth0 which provides user's name, picture, email, etc.

My question is if I can set this user info endpoint in my resource server, to fetch this information automatically or what is the best way to do that? I would like to have this informations in SecurityContextHolder or at least user's email and user's name.

@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http.authorizeRequests().anyRequest().permitAll()
        .and()
        .oauth2ResourceServer().jwt();
    return http.build()
}

and JWT decoder bean

@Bean
fun jwtDecoder(): JwtDecoder? {
    val jwtDecoder = JwtDecoders.fromOidcIssuerLocation<JwtDecoder>(issuer) as NimbusJwtDecoder
    val audienceValidator: OAuth2TokenValidator<Jwt> = AudienceValidator(audience)
    val withIssuer = JwtValidators.createDefaultWithIssuer(issuer)
    val withAudience: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(withIssuer, audienceValidator)
    jwtDecoder.setJwtValidator(withAudience)
    return jwtDecoder
}

File application.properties

spring.security.oauth2.resourceserver.jwt.issuer-uri=my-domain.com
spring.security.oauth2.resourceserver.jwt.audience=my-audience

EDIT This is payload of JWT received from Auth0

{
  "iss": "https://dev-abcdefgh.us.auth0.com/",
  "sub": "google-oauth2|353335637216442227159",
  "aud": [
    "my-audience",
    "https://dev-3ag8q43b.us.auth0.com/userinfo"
  ],
  "iat": 1663100248,
  "exp": 1663186648,
  "azp": "m01yBdKdQd5erBxriQde24ogfsdAsYvD",
  "scope": "openid profile email"
}
3 Answers

I would like to have this informations in SecurityContextHolder or at least user's email and user's name.

Have you seen what's inside your jwt token? Did you add openid scope in your authentication process? if so there should be an IdToken in your auth server response json body, inside IdToken jwt token claim there are various information about the user's data such user's name and email. Other user attributes can also be added by adding custom claim to your jwt token, after adding those claims then you can try to access it via SecurityContextHolder.

Reference link

You have to do little bit changes to make it working.

I will explain by step - by - step :

I have created an account and have registered a regular web application with the name test-app in the Auth0 portal.

Now, I took help of the resource links provided for Auth0 client and Resource Server by Auth0 to setup Spring boot app and these are given below.

  1. Auth0 Client Spring Boot App Quick Start
  2. Auth0 Resource Server Spring Boot App Quick Start

Now, I will explain through use cases.

I have created a Auth0 spring boot client (separate project).

application.properties :

server:
  port: 3000
spring:
  security:
    oauth2:
      client:
        registration:
          auth0:
            client-id: <<id>>
            client-secret: <<secret>>
            scope:
              - openid
              - profile
              - email
        provider:
          auth0:
            issuer-uri: https://<<name>>.us.auth0.com/

Note: You can find client id, client secret and issuer uri from Applications -> Open the app -> Settings.

Now, I need to extract ID token which contains the user info, so I have created a sample controller and have used OidcUser to get that token:

@RestController
public class Resource {

    @GetMapping("/token")
    public void profile(@AuthenticationPrincipal OidcUser oidcUser) {
        System.out.println(oidcUser.getIdToken().getTokenValue());
    }

}

As soon as I run the server and send the request to the /token, it will first redirect to the Auth0 login page. I have used my Google account to login and after successful login, it prints the ID JWT token.

Note: This client project is just show how I got the ID token. Not to confuse that resource server is also a client.

enter image description here

Now, coming to resource server, I have created a resource server Spring Boot App (separate project).

application.properties :

server:
  port: 3010
auth0:
  audience: https://<<name>>.auth0.com/api/v2/  
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://<<name>>.us.auth0.com/ 
          audiences:
          - https://<<name>>.us.auth0.com/api/v2/ 

SecurityConfig (You don't need to add any extra validator, i.e remove the AudienceValidator):

@EnableWebSecurity
public class SecurityConfig {

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuer;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll().and().oauth2ResourceServer().jwt(jwt -> jwtDecoder());
        return http.build();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuer);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
        jwtDecoder.setJwtValidator(withIssuer);
        return jwtDecoder;
    }
}

A sample Controller to show my case :

@RestController
public class ProfileController {

    @GetMapping("/profile")
    public void profile() {
        Jwt user = (Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println(user.getClaimAsString("email") + " " + user.getClaimAsString("name"));
    }
}

As soon as I have run the server, hit the uri /profile with Bearer ID token like this :

enter image description here

The Spring Security is consuming this Jwt automatically and setting the Principal at this Jwt Object and you can extract this via SecurityContextHolder.

Note: In the claims map, you have all of your user info.

enter image description here

Output :

enter image description here

Note: ID token are not safe to be used in any cases. It can be used only in the case where you want to show/get user profile data but it should be avoided for all other use cases.

Do not call user endpoint when building resource-server security-context

Auth0 can issue JWT access-token and JWTs can be decoded / validated on the resource-server without a round trip to the authorization-server.

Introducing a call to authorization-server user-info endpoint for each and every of your resource-server incoming request would be a drop in latency (and efficiency).

Do not use ID tokens as access-tokens

This is worst practice

Add user-info to access-tokens

In Auth0 management console, go to Auth Pipeline -> Rules and click Create to add a rule like:

function addEmailToAccessToken(user, context, callback) {
  context.accessToken['https://c4-soft.com/user'] = user;
  return callback(null, user, context);
}

Et voilĂ ! You now have a https://c4-soft.com/user private claim in access-tokens. You can (should?) narrow to the user attributes you actually need in your resource-server (what is accessed in your @PreAuthorize expressions for instance).

Configure your resource-server with JWT decoder

It is quite straight forward if you have spring-boot-starter-oauth2-resource-server on the classpath (which I believe you already have).

I recommand you go through the first three (very short) tutorials I wrote, you'll find usefull tips to make the best usage of this private claim you just added to access-tokens.

Related