Introduction to OAuth 2.0 in Modern Applications
Securing REST APIs in enterprise Java applications requires a robust authentication and authorization framework. OAuth 2.0 has become the industry standard protocol for authorization, providing delegated access to server resources on behalf of a resource owner. When combined with JSON Web Tokens for stateless authentication, Spring Security offers a powerful and flexible solution for protecting microservices and distributed systems. This guide covers the essential patterns, configurations, and best practices for implementing OAuth 2.0 with JWT in Spring Boot applications.
OAuth 2.0 separates the concerns of authentication and authorization by introducing distinct roles: the resource owner, the client application, the authorization server, and the resource server. This separation allows applications to grant limited access to user accounts on third-party services without exposing credentials. In enterprise environments, this architecture enables single sign-on across multiple services, fine-grained access control, and centralized token management.
OAuth 2.0 Grant Types and Flows
Authorization Code Flow
The authorization code flow is the most secure and commonly used grant type for server-side applications. The client redirects the user to the authorization server, which authenticates the user and returns an authorization code. The client then exchanges this code for an access token through a back-channel request. This flow prevents the access token from being exposed to the user agent and is suitable for confidential clients that can securely store client secrets.
In Spring Security, the authorization code flow is configured through the OAuth2 Login support. The framework handles the redirect, code exchange, and token storage automatically. For applications acting as OAuth2 clients, Spring Security provides built-in support for common providers like Google, GitHub, and Okta, while also allowing custom provider configuration for enterprise authorization servers.
Client Credentials Flow
The client credentials grant type is designed for machine-to-machine communication where no user context is required. The client authenticates directly with the authorization server using its own credentials and receives an access token. This flow is ideal for service-to-service communication in microservices architectures, batch processing jobs, and backend integrations where the application itself is the resource owner.
Spring Security simplifies client credentials configuration through the OAuth2AuthorizedClientManager and reactive equivalents. The framework manages token acquisition, caching, and automatic refresh, reducing boilerplate code in service-to-service calls.
JWT Token Structure and Claims
JSON Web Tokens consist of three Base64URL-encoded parts separated by dots: the header, the payload, and the signature. The header specifies the signing algorithm, the payload contains claims about the authenticated entity, and the signature ensures token integrity. JWTs are self-contained, meaning the resource server can validate tokens without contacting the authorization server, reducing latency in distributed systems.
Standard JWT claims include iss (issuer), sub (subject), aud (audience), exp (expiration time), iat (issued at), and jti (JWT ID). Custom claims can carry application-specific data such as user roles, permissions, and tenant identifiers. Here is an example of a typical JWT payload for a Spring Security application:
{
"iss": "https://auth.example.com",
"sub": "user-12345",
"aud": "api-gateway",
"exp": 1698451200,
"iat": 1698447600,
"scope": "read write",
"roles": ["ROLE_USER", "ROLE_ADMIN"],
"tenant_id": "org-001"
}
Spring Security Configuration for Resource Servers
Configuring a Spring Boot application as an OAuth2 resource server involves defining a SecurityFilterChain bean that validates incoming JWT tokens. The resource server verifies the token signature, checks expiration, and extracts authorities from token claims. Spring Security 6 uses the component-based security configuration approach with the @Configuration and @EnableWebSecurity annotations.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthConverter())
)
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.csrf(csrf -> csrf.disable());
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthConverter() {
JwtGrantedAuthoritiesConverter authoritiesConverter =
new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthorityPrefix("ROLE_");
authoritiesConverter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter converter =
new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
return converter;
}
}
The configuration above establishes stateless session management, disables CSRF for API endpoints, and maps JWT claims to Spring Security authorities. The JwtAuthenticationConverter extracts role information from the token's custom claims and converts them into granted authorities that Spring Security uses for access decisions.
Authorization Server Configuration
Spring Authorization Server is the official Spring project for building OAuth 2.0 authorization servers. It supports all standard grant types, token introspection, token revocation, and OpenID Connect. The authorization server issues access tokens, refresh tokens, and ID tokens based on registered client configurations and user authentication.
Key configuration elements include registered client definitions with allowed grant types and redirect URIs, token settings for access token lifetime and refresh token policies, and JWK source configuration for token signing. The authorization server exposes standard endpoints including /oauth2/authorize, /oauth2/token, and /.well-known/openid-configuration for discovery.
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient client = RegisteredClient
.withId(UUID.randomUUID().toString())
.clientId("api-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(
ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://localhost:8080/login/oauth2/code/api-client")
.scope(OidcScopes.OPENID)
.scope("read")
.scope("write")
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(15))
.refreshTokenTimeToLive(Duration.ofDays(7))
.reuseRefreshTokens(false)
.build())
.build();
return new InMemoryRegisteredClientRepository(client);
}
Best Practices for Production Deployments
Implementing OAuth 2.0 and JWT in production environments requires careful attention to security, performance, and operational concerns. Following established best practices helps prevent common vulnerabilities and ensures reliable token-based authentication across your services.
- Short-lived access tokens: Keep access token lifetimes between 5 and 15 minutes. Short-lived tokens limit the window of exposure if a token is compromised. Use refresh tokens for obtaining new access tokens without requiring re-authentication.
- Token rotation: Implement refresh token rotation where each use of a refresh token invalidates the previous one and issues a new refresh token. This detects token theft when a stolen refresh token is used after the legitimate client has already rotated it.
- Audience validation: Always validate the
audclaim in JWT tokens to ensure the token was intended for your specific resource server. This prevents tokens issued for one service from being used to access another. - Asymmetric signing: Use RSA or ECDSA algorithms for JWT signing in production. Asymmetric keys allow resource servers to verify tokens using the public key without needing access to the private signing key, improving security in distributed deployments.
- Scope-based access control: Define granular scopes that map to specific API operations. Combine scope validation with role-based access control for defense in depth. Validate both the token scope and user authorities before granting access to protected resources.
- HTTPS everywhere: All OAuth 2.0 communication must occur over TLS. Access tokens, refresh tokens, and authorization codes transmitted over unencrypted channels can be intercepted and replayed by attackers.
- Token revocation: Implement token revocation endpoints and maintain a deny list for invalidated tokens. While JWTs are stateless by design, critical security events like password changes or account compromises require immediate token invalidation.
- PKCE for public clients: Use Proof Key for Code Exchange with all public clients, including single-page applications and mobile apps. PKCE prevents authorization code interception attacks without requiring a client secret.
Conclusion
OAuth 2.0 and JWT provide a scalable, standards-based approach to securing Spring Boot applications. The combination of delegated authorization through OAuth 2.0 flows and stateless authentication via JWT tokens enables enterprise applications to implement robust security without sacrificing performance. Spring Security's comprehensive support for both resource servers and authorization servers simplifies implementation while maintaining flexibility for custom requirements. By following the configuration patterns and best practices outlined in this guide, development teams can build secure, maintainable authentication systems that scale across microservices architectures.