A Springboot library that provides everything the standard Spring Security JWT Bearer authentication offers, with the added power of built-in DPoP (Demonstration of Proof-of-Possession) support for enhanced token security. Simplify your Auth0 JWT authentication integration for Spring Boot APIs with Auth0-specific configuration and validation.
This library builds on top of the standard Spring Security JWT authentication, providing:
- Complete Spring Security JWT Functionality - All features from Spring Security JWT Bearer are available
- Built-in DPoP Support - Industry-leading proof-of-possession token security per RFC 9449
- Multiple Custom Domain (MCD) Support - Validate tokens from multiple Auth0 custom domains with static lists or dynamic resolution
- Extensible Caching - Pluggable
AuthCacheinterface for OIDC discovery and JWKS caching with distributed backend support (Redis, Memcached) - Auto-Configuration - Spring Boot auto-configuration with minimal setup
- Flexible Authentication Modes - Bearer-only, DPoP-only, or flexible mode supporting both
- Spring Boot 3.2+ (requires Java 17+) for Spring Boot integration
Add the dependency via Maven:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>auth0-springboot-api</artifactId>
<version>1.0.0-beta.1</version>
</dependency>or Gradle:
dependencies {
implementation 'com.auth0:auth0-springboot-api:1.0.0-beta.1'
}Add Auth0 authentication to your Spring Boot API:
1. Configure Auth0 properties in application.yml:
auth0:
domain: "your-tenant.auth0.com"
audience: "https://your-api-identifier"2. Create a Security Configuration class:
import com.auth0.spring.boot.Auth0AuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http, Auth0AuthenticationFilter authFilter) throws Exception {
return http.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/protected").authenticated()
.anyRequest().permitAll())
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}3. Create your API endpoints:
@RestController
public class ApiController {
@GetMapping("/open-endpoint")
public ResponseEntity<String> openEndpoint() {
return ResponseEntity.ok("This endpoint is available to all users.");
}
@GetMapping("/api/protected")
public ResponseEntity<String> protectedEndpoint(Authentication authentication) {
String userId = authentication.getName();
return ResponseEntity.ok("Hello, authenticated user: " + userId);
}
}That's it! Your API now validates JWT tokens from Auth0.
Note - If authorization header is null for protected endpoints, WWW-Authenticate header in response is not added.
Required Settings:
- Domain: Your Auth0 domain (e.g.,
my-app.auth0.com) - without thehttps://prefix - Audience: The API identifier configured in your Auth0 Dashboard
Optional Settings:
auth0:
domain: "your-tenant.auth0.com"
audience: "https://your-api-identifier"
dpopMode: ALLOWED # DISABLED, ALLOWED (default), REQUIRED
dpopIatOffsetSeconds: 300 # 300 s (default)
dpopIatLeewaySeconds: 30 # 30s (default)DPoP (Demonstration of Proof-of-Possession) is a security mechanism that binds access tokens to a cryptographic key, making them resistant to token theft and replay attacks. This library provides seamless DPoP integration for your Auth0-protected APIs.
Learn more about DPoP: Auth0 DPoP Documentation
Enable DPoP by setting the mode in your configuration:
auth0:
domain: "your-tenant.auth0.com"
audience: "https://your-api-identifier"
dpopMode: ALLOWED # Enable DPoP support while maintaining Bearer token compatibilityThat's it! Your API now supports DPoP tokens while maintaining backward compatibility with Bearer tokens.
For fine-grained control, configure DPoP behavior:
auth0:
domain: "your-tenant.auth0.com"
audience: "https://your-api-identifier"
dpopMode: REQUIRED # Only accept DPoP tokens
dpopIatOffsetSeconds: 300 # Allow 300 seconds offset for 'iat' claim (default)
dpopIatLeewaySeconds: 30 # 30 seconds leeway for time-based validation (default)Choose the right enforcement mode for your security requirements:
| Mode | Description |
|---|---|
ALLOWED (default) |
Accept both DPoP and Bearer tokens |
REQUIRED |
Only accept DPoP tokens, reject Bearer tokens |
DISABLED |
Standard JWT Bearer validation only |
Example client requests:
Bearer Token (traditional):
curl -H "Authorization: Bearer <jwt_token>" \
https://your-api.example.com/api/protectedDPoP Token (enhanced security):
curl -H "Authorization: DPoP <jwt_token>" \
-H "DPoP: <dpop_proof>" \
https://your-api.example.com/api/protectedFor tenant with multiple custom domains, the SDK can validate tokens from any of the configured issuers. There are three ways to configure domain resolution:
Configure a list of allowed issuer domains in application.yml:
auth0:
audience: "https://your-api-identifier"
domains:
- "login.acme.com"
- "auth.partner.com"
- "dev.example.com"You can also set a primary domain alongside the list:
auth0:
domain: "primary.auth0.com"
audience: "https://your-api-identifier"
domains:
- "brandA.acme.com"
- "brandB.acme.com"
- "brandC.acme.com"For scenarios where the allowed issuers depend on runtime context (e.g., tenant headers, database lookups), define a DomainResolver bean:
import com.auth0.DomainResolver;
@Configuration
public class McdConfig {
@Bean
public DomainResolver domainResolver(TenantService tenantService) {
return context -> {
// context.getHeaders() — request headers (lowercase keys)
// context.getUrl() — the API request URL
// context.getTokenIssuer() — unverified iss claim (routing hint only)
String tenantId = context.getHeaders().get("x-tenant-id");
String domain = tenantService.getDomain(tenantId);
return Collections.singletonList(domain);
};
}
}When a DomainResolver bean is present, it takes precedence over the static auth0.domains list. The single auth0.domain can still coexist as a fallback.
For single-tenant setups, just use the auth0.domain property:
auth0:
domain: "your-tenant.auth0.com"
audience: "https://your-api-identifier"The SDK caches OIDC discovery metadata and JWKS providers using a unified cache with key prefixes (discovery:{issuerUrl} and jwks:{jwksUri}). By default, it uses a thread-safe in-memory LRU cache.
You can replace this with a distributed cache (Redis, Memcached, etc.) by implementing the AuthCache interface:
import com.auth0.AuthCache;
public class RedisAuthCache implements AuthCache<Object> {
private final RedisTemplate<String, Object> redisTemplate;
private final Duration ttl;
public RedisAuthCache(RedisTemplate<String, Object> redisTemplate, Duration ttl) {
this.redisTemplate = redisTemplate;
this.ttl = ttl;
}
@Override
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public void put(String key, Object value) {
redisTemplate.opsForValue().set(key, value, ttl);
}
@Override
public void remove(String key) {
redisTemplate.delete(key);
}
@Override
public void clear() {
Set<String> keys = redisTemplate.keys("discovery:*");
if (keys != null) redisTemplate.delete(keys);
keys = redisTemplate.keys("jwks:*");
if (keys != null) redisTemplate.delete(keys);
}
@Override
public int size() {
return 0; // approximate
}
}Then define it as a Spring bean — the auto-configuration picks it up automatically and wires it into AuthOptions. No need to create your own AuthClient bean:
@Configuration
public class CacheConfig {
@Bean
public AuthCache<Object> authCache(RedisTemplate<String, Object> redisTemplate) {
return new RedisAuthCache(redisTemplate, Duration.ofMinutes(10));
}
}When an AuthCache bean is present, the cacheMaxEntries and cacheTtlSeconds YAML properties are ignored — your implementation controls its own eviction and TTL.
If no custom cache is provided, the built-in in-memory cache is used with these defaults:
auth0:
cacheMaxEntries: 100 # max entries before LRU eviction
cacheTtlSeconds: 600 # 10-minute TTL per entryAccess JWT claims directly through Auth0AuthenticationToken's clean API:
@RestController
public class UserController {
@GetMapping("/api/user-profile")
public ResponseEntity<Map<String, Object>> userProfile(Authentication authentication) {
// Cast to Auth0AuthenticationToken to access Auth0-specific features
Auth0AuthenticationToken auth0Token = (Auth0AuthenticationToken) authentication;
Map<String, Object> profile = new HashMap<>();
profile.put("userId", auth0Token.getClaim("sub"));
profile.put("email", auth0Token.getClaim("email"));
profile.put("scopes", auth0Token.getScopes()); // Set<String> of scopes
profile.put("authorities", authentication.getAuthorities()); // Spring Security authorities
return ResponseEntity.ok(profile);
}
@GetMapping("/api/admin")
public ResponseEntity<String> adminEndpoint(Authentication authentication) {
// Validate custom claims using individual claim access
Auth0AuthenticationToken auth0Token = (Auth0AuthenticationToken) authentication;
String userRole = (String) auth0Token.getClaim("role");
if ("admin".equals(userRole)) {
return ResponseEntity.ok("Admin access granted");
} else {
return ResponseEntity.status(403).body("Admin role required");
}
}
}Implement scope-based access control:
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http, Auth0AuthenticationFilter authFilter) throws Exception {
return http.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").authenticated()
.requestMatchers("/api/users/**").hasAnyAuthority("SCOPE_read:messages")
.requestMatchers("/api/protected").authenticated()
.anyRequest().permitAll())
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}For custom scope validation in controllers:
@Component
public class ScopeValidator {
public boolean hasRequiredScopes(Authentication authentication, String... requiredScopes) {
if (!(authentication instanceof Auth0AuthenticationToken)) {
return false;
}
Auth0AuthenticationToken auth0Token = (Auth0AuthenticationToken) authentication;
Set<String> tokenScopes = auth0Token.getScopes();
return tokenScopes.containsAll(Arrays.asList(requiredScopes));
}
}
@RestController
public class AdminController {
@Autowired
private ScopeValidator scopeValidator;
@GetMapping("/api/admin")
public ResponseEntity<Map<String, Object>> adminEndpoint(Authentication authentication) {
if (!scopeValidator.hasRequiredScopes(authentication, "admin")) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(Map.of("error", "insufficient_scope"));
}
return ResponseEntity.ok(Map.of("message", "Admin access granted"));
}
}For comprehensive examples and use cases, see the playground application:
@RestController
public class ApiController {
@GetMapping("/open")
public String openEndpoint() {
return "This endpoint is available to all users.";
}
@GetMapping("/protected")
public String protectedEndpoint(Authentication auth) {
return "Hello, " + auth.getName() + "!";
}
}@GetMapping("/conditional")
public ResponseEntity<String> conditionalEndpoint(Authentication authentication) {
Auth0AuthenticationToken auth0Token = (Auth0AuthenticationToken) authentication;
String userRole = (String) auth0Token.getClaim("role");
if ("admin".equals(userRole)) {
return ResponseEntity.ok("Admin access granted");
} else {
return ResponseEntity.status(403).body("Admin access required");
}
}Clone the repository and build the project:
git clone https://github.com/auth0/auth0-auth-java.git
cd auth0-auth-java
./gradlew clean buildThe repository includes a playground application for testing both standard JWT Bearer and DPoP authentication:
-
Configure Auth0 settings in
auth0-springboot-api-playground/src/main/resources/application.yml:auth0: domain: "your-tenant.auth0.com" audience: "https://your-api-identifier" dpopMode: ALLOWED
-
Run the playground:
./gradlew :auth0-springboot-api-playground:bootRun
-
Access the application:
- Application:
http://localhost:8080 - Open endpoint: GET
/open-endpoint(no authentication required) - Protected endpoint: GET
/api/protected(requires authentication)
- Application:
1. Obtain a JWT token from Auth0:
curl -X POST https://your-tenant.auth0.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"audience": "https://your-api-identifier",
"grant_type": "client_credentials"
}'2. Test Bearer authentication:
curl -H "Authorization: Bearer <your-jwt-token>" \
http://localhost:8080/api/protected3. Test DPoP authentication (requires DPoP-bound token):
curl -H "Authorization: DPoP <your-dpop-bound-token>" \
-H "DPoP: <your-dpop-proof>" \
http://localhost:8080/api/protectedWe appreciate your contributions! Please review our contribution guidelines before submitting pull requests.
- Read the Auth0 General Contribution Guidelines
- Read the Auth0 Code of Conduct
- Ensure all tests pass
- Add tests for new functionality
- Update documentation as needed
- Sign all commits
If you have questions or need help:
- Check the Auth0 Documentation
- Visit the Auth0 Community
- Report issues on GitHub Issues
Copyright 2025 Okta, Inc. This project is licensed under the Apache License 2.0 - see the LICENSE file for details. Authors Okta Inc.

Auth0 is an easy-to-implement, adaptable authentication and authorization platform. To learn more check out Why Auth0?
This project is licensed under the Apache License 2.0. See the LICENSE file for more info.