I have a Spring Boot 3 service that uses Auth0/Okta to secure its API. The users get a token and can call my endpoints. I am using these parameters in my application.yaml :
okta:
oauth2:
audience:
issuer:
My service also needs to be able to call Auth0, to be able to create a user for instance, using OAuth2 token
Things are properly setup on the Auth0 side, because I am able to make the call with Postman, after retrieving a token using clientId and clientSecret that were issued for my service.
I assumed it would be straightforward to do with the SDK and starter and embed this in my service, but I am not finding the way to achieve that :
and through a private key :
I could not find any resource on how to achieve something similar, with Oauth2.
When I try it "manually", by setting up more code myself, I am failing to get a token, because Auth0 requires an audience
parameter, that is not supported out of the box by Spring Security (https://github.com/spring-projects/spring-security/issues/6569)
I was expecting this to be very easy given how Spring Boot and Auth0 are popular, but it turns out I am not finding a single example of this working more or less out of the box with Spring Boot 3 / Spring Security 6..
In the end, these were useful resources :
Did I miss anything in the docs, or is there truly no better way of calling the Auth0 API with OAuth2 ?
I just went through this exercise, so allow me to help.
First, let me say that I went down the route of using the okta SDK but I didn't have a good experience with it.
<dependency>
<groupId>com.okta</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
I also used Auth0 by Okta oauth2 service since it is a free developer oriented platform.
My project used Spring security dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Since you are only interested in the Resource Server made a quick project for that.
Add this to your application.yml.
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${auth0audience-issuer-uri}
audiences: ${auth0audience-audience}
client:
registration:
auth0:
client-id: ${auth0audience-client-id}
client-secret: ${auth0audience-client-secret}
authorization-grant-type: client_credentials
provider: auth0
provider:
auth0:
issuer-uri: ${auth0audience-issuer-uri}
Then configure the SecurityFilterChain
to require authentication and use JWT
access tokens for authentication.
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth
.anyRequest()
.authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())).build();
}
}
As you have found out though, the difficulty really is in accessing it through WebClient
. Spring security must be configured to add the audience
parameter to the request headers when requesting an access token to use with the API. This is important because the access token must have the aud
property when it is issued. This really means configurating the OAuth2AuthorizedClientManager
to have a DefaultClientCredentialsTokenResponseClient
that is configured to add the audience
parameter to the request for an access token. This client is invoked auto-magically in the background when there is a need for a new access token.
@Configuration
public class WebClientConfiguration {
@Value("${spring.security.oauth2.resourceserver.jwt.audiences}")
private String audience;
@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.baseUrl("http://localhost:8080/")
.build();
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials((builder) ->
builder.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient())
.build())
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
new OAuth2ClientCredentialsGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(source -> CollectionUtils.toMultiValueMap(Collections.singletonMap("audience", Collections.singletonList(audience))));
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
return accessTokenResponseClient;
}
}
Then you can use it.
@SpringBootApplication
public class Auth0WebClientAudienceApplication implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Auth0WebClientAudienceApplication.class, args);
}
@Autowired
WebClient webClient;
@Override
public void run(ApplicationArguments args) throws Exception {
List<String> r = webClient.get().uri("movies")
.attributes(clientRegistrationId("auth0"))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {
}).block();
System.out.println("movies = " + r);
}
}
The special need here is the line .attributes(clientRegistrationId("auth0"))
which tells the webclient which client the client manager should use to get the access token before making the call to the API. It's all very well hidden with minimal documentation.
The code for the API is nothing special. This example does not attempt to demonstrate authorization.
@RestController
@RequestMapping
public class MovieApi {
@GetMapping("movies")
public List<String> getMovies() {
SecurityContext securityContext = SecurityContextHolder.getContext();
if (!(securityContext instanceof AnonymousAuthenticationToken)) {
return List.of("movie1", "movie2");
}
return Collections.emptyList();
}
}
Git repo of this code at Auth0WebClientAudience.
Spring Security Framework has some documenation at OAuth 2.0.