JWT vs Opaque Access Tokens: Use Both With Spring Boot
Tutorial: Learn how to use JWT and opaque access with Spring Boot.
JWT vs Opaque Access Tokens: Use Both With Spring Boot
Tutorial: Learn how to use JWT and opaque access with Spring Boot.
Wolf
Hi, a small note on the configuration described in the video,
starting spring boot 2.4, you need to add the openid scope into the
configuration like so:
spring.security.oauth2.client.registration.okta.scope=openid
This is because of the upgrade to spring security 5.4, which brings the change:
Refined ClientRegistrations to not default scopes to the OIDC scopes_supported attribute
Announced
in the What’s new section of spring
security’s reference documentation.
Brian Demers
Good catch! I’ll update the post!
George Christman
When the frontend is using the PKCE flow and you have no client secret, how do you validate the access_token with Spring Security?
Brian Demers
The /introspect
endpoint doesn’t always require a client secret (it depends on the type of application you have setup). This page goes into more details: https://developer.okta.com/…
Does that answer your question?
David Pratt
Thanks for this excellent walkthrough. It was almost exactly what I was looking for. Using PKCE with Okta myself but still struggling. I don’t suppose this flow is built into Spring or coming soon? I tried to just leave the secret blank or out of my properties file but that did not suffice. Thanks!
Brian Demers
Spring has limited built in support for PKCE. If you don’t set a client-secret Spring Security will attempt to use PKCE (with an Auth Code flow, i.e. using .oauth2Login()
)
There is an outstanding issue to support PKCE for confidential clients with an auth code flow too
This is very helpful. Thank you Brian!
When I have create a RequestMapping inside my Rest Controller with @AuthenticationPrincipal, I get oidcUser as null ? Doesn’t AuthenticationPrincipal work when ExampleWebSecurityConfigurerAdapter is configured with http.oauth2ResourceServer().authenticationManagerResolver(customAuthenticationManager());
Sample Rest Mapping
@GetMapping(value="/greeting", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String greeting(@AuthenticationPrincipal OidcUser oidcUser) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication.getPrincipal() instanceof DefaultOAuth2AuthenticatedPrincipal){
DefaultOAuth2AuthenticatedPrincipal principal = (DefaultOAuth2AuthenticatedPrincipal)authentication.getPrincipal();
System.out.println("attributes : " + principal.getAttributes());
System.out.println("authorities : " + principal.getAuthorities());
}
return "Hello world: greeting";
}
Hi @vdc305!
This is one of those gotchas. For a resource server, the principal is a different type of object (which reflects the different auth being used). It will either be a JwtAuthenticationToken
or a BearerTokenAuthentication
. (IIRC, they share the same parent class, so you should be able to inject that too if you need to support both)
If you are unsure what you have (and are doing some quick debugging) you can inject Principal
and then inspect the object with your debugger.
@bdemers I figured out the issue its because of how the AuthenticationManagerResolver uses the AuthenticationProvider via jwt() and `opaque() method.
I tried to create a simple resolver to BearerTokenResolver and I was able to get @AuthenticationPrincipal working. I will try it out with the jwt() vs opaque() and apply it to authenticationManagers.put();
@bdemers As per the article GitHub - okta/okta-jwt-verifier-java: okta-jwt-verifier-java, okta-jwt-verifier maven dependency can be used to validate the jwt. The public keys (JWKS) are used to validate the JWTs and cached automatically via blocking calls at startup and whenever the keys are rotated. How do I achieve the caching of the JWKS based on your example ? I was able to validate jwt locally (GET) as well as remotely (POST or PATCH for updating user credentials) based on your example, but I am wondering if it makes sense to cache the JWK too for local validation?
Hi @vdc305!
I’m not sure I understand your question.
If you are using Spring’s OAuth libraries, you don’t need to use Okta JWT verifier, as the logic to deal with the JWKS is handled by Spring Security automatically.
Similarly, for other Java-based apps using the Okta JWT Verifier, that library handles the caching for you too.
Can you explain your use-case a bit more?
I am using spring Oauth libraries for this example and not the Okta jwt verifier. But after I read the caching info as per GitHub - okta/okta-jwt-verifier-java: okta-jwt-verifier-java I was wondering how to achieve the. same that with spring oauth libraries. In your local jwt validation method ie jwt() you are setting NimbusJwtDecoder as below. This works fine for me as I am able to do local jwt validation.
JwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
I am not sure if the below code base makes sense for JWK caching.
DefaultJWTProcessor configureJwksCache(OAuth2IdpConfig config) {
try {
var jwkSetCache =
new DefaultJWKSetCache(
config.jwkCacheTtl.toMinutes(),
config.jwkCacheRefresh.toMinutes(),
TimeUnit.MINUTES);
var jwsKeySelector =
JWSAlgorithmFamilyJWSKeySelector.fromJWKSource(
new RemoteJWKSet<>(
new URL(config.jwkSetUri), null, jwkSetCache));
var jwtProcessor = new DefaultJWTProcessor();
jwtProcessor.setJWSKeySelector(jwsKeySelector);
return jwtProcessor;
} catch (KeySourceException | MalformedURLException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
var jwtDecoder =
new NimbusJwtDecoder(configureJwksCache(config));