OAuth2 is Not Just JWTs

If your “OAuth2 implementation” is just a /login endpoint that returns a JWT, you’re doing it wrong. Modern authentication requires:

  • PKCE for public clients
  • Token rotation for security
  • Zero-trust validation for microservices

Spring Security 7 makes all of this straightforward.


OAuth2 Flow with Spring Security


The Authorization Code Flow with PKCE

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. It’s now mandatory for SPAs and mobile apps.

Spring Configuration

``

`java @Configuration @EnableWebSecurity public class SecurityConfig {

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/**").authenticated()
            .anyRequest().permitAll()
        )
        .oauth2Login(oauth -> oauth
            .authorizationEndpoint(endpoint -> 
                endpoint.authorizationRequestResolver(pkceResolver())
            )
        )
        .oauth2ResourceServer(oauth -> oauth.jwt());
    return http.build();
} } ```

Why PKCE Matters

Without PKCE, an attacker intercepting the authorization code can exchange it for tokens. PKCE adds a cryptographic challenge that only the original client can complete.


JWT Validation in Microservices

Don’t trust JWTs blindly. Always validate:

@Bean
JwtDecoder jwtDecoder() {
    NimbusJwtDecoder decoder = NimbusJwtDecoder
        .withJwkSetUri("https://auth.example.com/.well-known/jwks.json")
        .build();
    
    decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
        new JwtTimestampValidator(),
        new JwtIssuerValidator("https://auth.example.com"),
        audienceValidator()
    ));
    
    return decoder;
}

OAuth2TokenValidator<Jwt> audienceValidator() {
    return token -> {
        List<String> audience = token.getAudience();
        if (audience.contains("api://myservice")) {
            return OAuth2TokenValidatorResult.success();
        }
        return OAuth2TokenValidatorResult.failure(
            new OAuth2Error("invalid_token", "Invalid audience", null)
        );
    };
}

Token Rotation for Refresh Tokens

Refresh tokens should be single-use and rotate on every refresh:

@Bean
OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRepo,
        OAuth2AuthorizedClientRepository authorizedClientRepo) {
    
    var authorizedClientProvider = 
        OAuth2AuthorizedClientProviderBuilder.builder()
            .authorizationCode()
            .refreshToken(builder -> builder
                .clockSkew(Duration.ofMinutes(1))
            )
            .build();
    
    var manager = new DefaultOAuth2AuthorizedClientManager(
        clientRepo, authorizedClientRepo
    );
    manager.setAuthorizedClientProvider(authorizedClientProvider);
    
    return manager;
}

This automatically handles token refresh and rotation.


Zero-Trust: Never Trust, Always Verify

In a zero-trust architecture, every request must be validated, even from internal services.

Method Security

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {

    @Bean
    MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = 
            new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return handler;
    }
}

@Service
public class OrderService {
    
    @PreAuthorize("hasAuthority('SCOPE_orders.write')")
    public void createOrder(Order order) {
        // Only callable with correct scope
    }
}

Common Security Mistakes

Mistake Risk Fix
Storing JWTs in localStorage XSS attacks can steal tokens Use httpOnly cookies
Not validating token audience Token reuse across services Always check aud claim
Long-lived access tokens Increased exposure window Keep access tokens < 15 minutes

Conclusion

OAuth2 in 2026 is about defense in depth: PKCE for clients, proper validation for services, and zero-trust for internal communication. Spring Security 7 provides the tools—use them correctly.

Want to secure your deployments? Check out [Kubernetes-Native Spring Boot](# or Production Checklist for operational security.