From a3949c198cc5fd6da78cbf1234feebfc59b11da3 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Thu, 26 Feb 2026 10:49:45 +0530 Subject: [PATCH 1/2] Feat: Added Transitive dependency --- .../com/auth0/AbstractAuthentication.java | 2 - .../com/auth0/AllowedDPoPAuthentication.java | 3 - .../java/com/auth0/{cache => }/AuthCache.java | 2 +- .../src/main/java/com/auth0/AuthClient.java | 4 - .../main/java/com/auth0/AuthConstants.java | 2 +- .../{validators => }/ClaimValidator.java | 2 +- .../{validators => }/DPoPProofValidator.java | 10 +- .../com/auth0/DisabledDPoPAuthentication.java | 3 - .../auth0/{cache => }/InMemoryAuthCache.java | 2 +- .../auth0/{validators => }/JWTValidator.java | 15 +- .../OidcDiscoveryFetcher.java | 3 +- .../com/auth0/RequiredDPoPAuthentication.java | 4 - .../java/com/auth0/models/AuthOptions.java | 2 +- .../com/auth0/AbstractAuthenticationTest.java | 2 - .../auth0/AllowedDPoPAuthenticationTest.java | 2 - .../{validators => }/ClaimValidatorTest.java | 2 +- .../DPoPProofValidatorTest.java | 2 +- .../auth0/DisabledDPoPAuthenticationTest.java | 1 - .../{validators => }/JWTValidatorTest.java | 3 +- .../OidcDiscoveryFetcherTest.java | 3 +- .../auth0/RequiredDPoPAuthenticationTest.java | 2 - .../auth0/cache/InMemoryAuthCacheTest.java | 1 + .../com/auth0/models/AuthOptionsTest.java | 4 +- auth0-springboot-api-playground/build.gradle | 6 + .../playground/McdDomainResolverExample.java | 31 ++-- .../auth0/playground/ProfileController.java | 2 +- .../auth0/playground/RedisCacheExample.java | 154 ++++++++++++++++++ auth0-springboot-api/build.gradle | 2 +- .../spring/boot/Auth0AutoConfiguration.java | 49 +++--- .../spring/boot/Auth0DomainResolver.java | 69 -------- .../spring/boot/Auth0RequestContext.java | 76 --------- .../boot/Auth0AutoConfigurationTest.java | 9 +- 32 files changed, 232 insertions(+), 242 deletions(-) rename auth0-api-java/src/main/java/com/auth0/{cache => }/AuthCache.java (98%) rename auth0-api-java/src/main/java/com/auth0/{validators => }/ClaimValidator.java (99%) rename auth0-api-java/src/main/java/com/auth0/{validators => }/DPoPProofValidator.java (96%) rename auth0-api-java/src/main/java/com/auth0/{cache => }/InMemoryAuthCache.java (99%) rename auth0-api-java/src/main/java/com/auth0/{validators => }/JWTValidator.java (96%) rename auth0-api-java/src/main/java/com/auth0/{validators => }/OidcDiscoveryFetcher.java (98%) rename auth0-api-java/src/test/java/com/auth0/{validators => }/ClaimValidatorTest.java (99%) rename auth0-api-java/src/test/java/com/auth0/{validators => }/DPoPProofValidatorTest.java (99%) rename auth0-api-java/src/test/java/com/auth0/{validators => }/JWTValidatorTest.java (99%) rename auth0-api-java/src/test/java/com/auth0/{validators => }/OidcDiscoveryFetcherTest.java (98%) create mode 100644 auth0-springboot-api-playground/src/main/java/com/auth0/playground/RedisCacheExample.java delete mode 100644 auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0DomainResolver.java delete mode 100644 auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0RequestContext.java diff --git a/auth0-api-java/src/main/java/com/auth0/AbstractAuthentication.java b/auth0-api-java/src/main/java/com/auth0/AbstractAuthentication.java index 3e84190..6cdc2a2 100644 --- a/auth0-api-java/src/main/java/com/auth0/AbstractAuthentication.java +++ b/auth0-api-java/src/main/java/com/auth0/AbstractAuthentication.java @@ -7,8 +7,6 @@ import com.auth0.models.AuthToken; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.DPoPProofValidator; -import com.auth0.validators.JWTValidator; import java.util.HashMap; import java.util.List; diff --git a/auth0-api-java/src/main/java/com/auth0/AllowedDPoPAuthentication.java b/auth0-api-java/src/main/java/com/auth0/AllowedDPoPAuthentication.java index cc40a49..851ff78 100644 --- a/auth0-api-java/src/main/java/com/auth0/AllowedDPoPAuthentication.java +++ b/auth0-api-java/src/main/java/com/auth0/AllowedDPoPAuthentication.java @@ -6,10 +6,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.DPoPProofValidator; -import com.auth0.validators.JWTValidator; -import java.util.Map; class AllowedDPoPAuthentication extends AbstractAuthentication { public AllowedDPoPAuthentication(JWTValidator jwtValidator, diff --git a/auth0-api-java/src/main/java/com/auth0/cache/AuthCache.java b/auth0-api-java/src/main/java/com/auth0/AuthCache.java similarity index 98% rename from auth0-api-java/src/main/java/com/auth0/cache/AuthCache.java rename to auth0-api-java/src/main/java/com/auth0/AuthCache.java index df7fafd..8cb1a96 100644 --- a/auth0-api-java/src/main/java/com/auth0/cache/AuthCache.java +++ b/auth0-api-java/src/main/java/com/auth0/AuthCache.java @@ -1,4 +1,4 @@ -package com.auth0.cache; +package com.auth0; /** * Cache abstraction for storing authentication-related data such as diff --git a/auth0-api-java/src/main/java/com/auth0/AuthClient.java b/auth0-api-java/src/main/java/com/auth0/AuthClient.java index 7c91086..6831768 100644 --- a/auth0-api-java/src/main/java/com/auth0/AuthClient.java +++ b/auth0-api-java/src/main/java/com/auth0/AuthClient.java @@ -4,10 +4,6 @@ import com.auth0.models.AuthenticationContext; import com.auth0.models.AuthOptions; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.DPoPProofValidator; -import com.auth0.validators.JWTValidator; - -import java.util.Map; public class AuthClient { diff --git a/auth0-api-java/src/main/java/com/auth0/AuthConstants.java b/auth0-api-java/src/main/java/com/auth0/AuthConstants.java index f4b17a1..48f62b8 100644 --- a/auth0-api-java/src/main/java/com/auth0/AuthConstants.java +++ b/auth0-api-java/src/main/java/com/auth0/AuthConstants.java @@ -1,6 +1,6 @@ package com.auth0; -public class AuthConstants { +class AuthConstants { public static final String AUTHORIZATION_HEADER = "authorization"; public static final String DPOP_HEADER = "dpop"; public static final String BEARER_SCHEME = "bearer"; diff --git a/auth0-api-java/src/main/java/com/auth0/validators/ClaimValidator.java b/auth0-api-java/src/main/java/com/auth0/ClaimValidator.java similarity index 99% rename from auth0-api-java/src/main/java/com/auth0/validators/ClaimValidator.java rename to auth0-api-java/src/main/java/com/auth0/ClaimValidator.java index 6a03216..9db74b0 100644 --- a/auth0-api-java/src/main/java/com/auth0/validators/ClaimValidator.java +++ b/auth0-api-java/src/main/java/com/auth0/ClaimValidator.java @@ -1,4 +1,4 @@ -package com.auth0.validators; +package com.auth0; import com.auth0.exception.*; import com.auth0.jwt.interfaces.DecodedJWT; diff --git a/auth0-api-java/src/main/java/com/auth0/validators/DPoPProofValidator.java b/auth0-api-java/src/main/java/com/auth0/DPoPProofValidator.java similarity index 96% rename from auth0-api-java/src/main/java/com/auth0/validators/DPoPProofValidator.java rename to auth0-api-java/src/main/java/com/auth0/DPoPProofValidator.java index 5169417..61c6c88 100644 --- a/auth0-api-java/src/main/java/com/auth0/validators/DPoPProofValidator.java +++ b/auth0-api-java/src/main/java/com/auth0/DPoPProofValidator.java @@ -1,4 +1,4 @@ -package com.auth0.validators; +package com.auth0; import com.auth0.exception.BaseAuthException; import com.auth0.exception.InvalidDpopProofException; @@ -20,13 +20,13 @@ import java.time.Instant; import java.util.*; -public class DPoPProofValidator { +class DPoPProofValidator { private final AuthOptions options; private final ObjectMapper objectMapper = new ObjectMapper();; - public DPoPProofValidator(AuthOptions options) { + DPoPProofValidator(AuthOptions options) { this.options = options; } @@ -38,7 +38,7 @@ public DPoPProofValidator(AuthOptions options) { * @param requestInfo HTTP request info: method and URL * @throws BaseAuthException if the DPoP proof is invalid. */ - public void validate(String dpopProof, DecodedJWT decodedJwtToken, HttpRequestInfo requestInfo) + void validate(String dpopProof, DecodedJWT decodedJwtToken, HttpRequestInfo requestInfo) throws BaseAuthException { DecodedJWT proofJwt = decodeDPoP(dpopProof); @@ -197,7 +197,7 @@ String calculateJwkThumbprint(Map jwk) throws BaseAuthException } } - public static ECPublicKey convertJwkToEcPublicKey(Map jwkMap) + static ECPublicKey convertJwkToEcPublicKey(Map jwkMap) throws JwkException { Jwk jwk = Jwk.fromValues(jwkMap); diff --git a/auth0-api-java/src/main/java/com/auth0/DisabledDPoPAuthentication.java b/auth0-api-java/src/main/java/com/auth0/DisabledDPoPAuthentication.java index 9b29780..b8f2ae9 100644 --- a/auth0-api-java/src/main/java/com/auth0/DisabledDPoPAuthentication.java +++ b/auth0-api-java/src/main/java/com/auth0/DisabledDPoPAuthentication.java @@ -5,9 +5,6 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.JWTValidator; - -import java.util.Map; class DisabledDPoPAuthentication extends AbstractAuthentication { diff --git a/auth0-api-java/src/main/java/com/auth0/cache/InMemoryAuthCache.java b/auth0-api-java/src/main/java/com/auth0/InMemoryAuthCache.java similarity index 99% rename from auth0-api-java/src/main/java/com/auth0/cache/InMemoryAuthCache.java rename to auth0-api-java/src/main/java/com/auth0/InMemoryAuthCache.java index 1a56b05..96a3a60 100644 --- a/auth0-api-java/src/main/java/com/auth0/cache/InMemoryAuthCache.java +++ b/auth0-api-java/src/main/java/com/auth0/InMemoryAuthCache.java @@ -1,4 +1,4 @@ -package com.auth0.cache; +package com.auth0; import java.util.LinkedHashMap; import java.util.Map; diff --git a/auth0-api-java/src/main/java/com/auth0/validators/JWTValidator.java b/auth0-api-java/src/main/java/com/auth0/JWTValidator.java similarity index 96% rename from auth0-api-java/src/main/java/com/auth0/validators/JWTValidator.java rename to auth0-api-java/src/main/java/com/auth0/JWTValidator.java index 052ced4..fb732f0 100644 --- a/auth0-api-java/src/main/java/com/auth0/validators/JWTValidator.java +++ b/auth0-api-java/src/main/java/com/auth0/JWTValidator.java @@ -1,7 +1,5 @@ -package com.auth0.validators; +package com.auth0; -import com.auth0.cache.AuthCache; -import com.auth0.cache.InMemoryAuthCache; import com.auth0.exception.BaseAuthException; import com.auth0.exception.MissingRequiredArgumentException; import com.auth0.exception.VerifyAccessTokenException; @@ -17,6 +15,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.models.HttpRequestInfo; import com.auth0.models.RequestContext; +import com.auth0.OidcDiscoveryFetcher; import java.net.MalformedURLException; import java.net.URL; @@ -32,7 +31,7 @@ * algorithm * and JWKS (JSON Web Key Set) for public key retrieval. */ -public class JWTValidator { +class JWTValidator { static final String JWKS_CACHE_PREFIX = "jwks:"; @@ -47,7 +46,7 @@ public class JWTValidator { * * @param authOptions Authentication options containing domain and audience */ - public JWTValidator(AuthOptions authOptions) { + JWTValidator(AuthOptions authOptions) { if (authOptions == null) { throw new IllegalArgumentException("AuthOptions cannot be null"); } @@ -66,7 +65,7 @@ public JWTValidator(AuthOptions authOptions) { * @param authOptions Authentication options containing domain and audience * @param jwkProvider Custom JwkProvider for key retrieval */ - public JWTValidator(AuthOptions authOptions, JwkProvider jwkProvider) { + JWTValidator(AuthOptions authOptions, JwkProvider jwkProvider) { if (authOptions == null) { throw new IllegalArgumentException("AuthOptions cannot be null"); } @@ -117,7 +116,7 @@ private static AuthCache resolveCache(AuthOptions options) { * @return the decoded and verified JWT * @throws BaseAuthException if validation fails */ - public DecodedJWT validateToken(String token, HttpRequestInfo httpRequestInfo) throws BaseAuthException { + public DecodedJWT validateToken(String token, HttpRequestInfo httpRequestInfo) throws BaseAuthException { if (token == null || token.trim().isEmpty()) { throw new MissingRequiredArgumentException("access_token"); @@ -167,7 +166,7 @@ public DecodedJWT validateToken(String token, HttpRequestInfo httpRequestInfo) t /** * Validates a JWT and ensures all required scopes are present. */ - public DecodedJWT validateTokenWithRequiredScopes(String token, HttpRequestInfo httpRequestInfo, String... requiredScopes) throws BaseAuthException { + DecodedJWT validateTokenWithRequiredScopes(String token, HttpRequestInfo httpRequestInfo, String... requiredScopes) throws BaseAuthException { DecodedJWT jwt = validateToken(token, httpRequestInfo); try { ClaimValidator.checkRequiredScopes(jwt, requiredScopes); diff --git a/auth0-api-java/src/main/java/com/auth0/validators/OidcDiscoveryFetcher.java b/auth0-api-java/src/main/java/com/auth0/OidcDiscoveryFetcher.java similarity index 98% rename from auth0-api-java/src/main/java/com/auth0/validators/OidcDiscoveryFetcher.java rename to auth0-api-java/src/main/java/com/auth0/OidcDiscoveryFetcher.java index 2cf73f4..e6530f6 100644 --- a/auth0-api-java/src/main/java/com/auth0/validators/OidcDiscoveryFetcher.java +++ b/auth0-api-java/src/main/java/com/auth0/OidcDiscoveryFetcher.java @@ -1,6 +1,5 @@ -package com.auth0.validators; +package com.auth0; -import com.auth0.cache.AuthCache; import com.auth0.exception.VerifyAccessTokenException; import com.auth0.models.OidcMetadata; import com.fasterxml.jackson.databind.JsonNode; diff --git a/auth0-api-java/src/main/java/com/auth0/RequiredDPoPAuthentication.java b/auth0-api-java/src/main/java/com/auth0/RequiredDPoPAuthentication.java index 8dd63f7..eb135a5 100644 --- a/auth0-api-java/src/main/java/com/auth0/RequiredDPoPAuthentication.java +++ b/auth0-api-java/src/main/java/com/auth0/RequiredDPoPAuthentication.java @@ -5,10 +5,6 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.DPoPProofValidator; -import com.auth0.validators.JWTValidator; - -import java.util.Map; class RequiredDPoPAuthentication extends AbstractAuthentication { diff --git a/auth0-api-java/src/main/java/com/auth0/models/AuthOptions.java b/auth0-api-java/src/main/java/com/auth0/models/AuthOptions.java index 5771701..95747ea 100644 --- a/auth0-api-java/src/main/java/com/auth0/models/AuthOptions.java +++ b/auth0-api-java/src/main/java/com/auth0/models/AuthOptions.java @@ -1,7 +1,7 @@ package com.auth0.models; import com.auth0.DomainResolver; -import com.auth0.cache.AuthCache; +import com.auth0.AuthCache; import com.auth0.enums.DPoPMode; import java.util.ArrayList; diff --git a/auth0-api-java/src/test/java/com/auth0/AbstractAuthenticationTest.java b/auth0-api-java/src/test/java/com/auth0/AbstractAuthenticationTest.java index fa75587..aad6492 100644 --- a/auth0-api-java/src/test/java/com/auth0/AbstractAuthenticationTest.java +++ b/auth0-api-java/src/test/java/com/auth0/AbstractAuthenticationTest.java @@ -8,8 +8,6 @@ import com.auth0.models.AuthToken; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.DPoPProofValidator; -import com.auth0.validators.JWTValidator; import org.junit.Before; import org.junit.Test; diff --git a/auth0-api-java/src/test/java/com/auth0/AllowedDPoPAuthenticationTest.java b/auth0-api-java/src/test/java/com/auth0/AllowedDPoPAuthenticationTest.java index 2320a00..b7dfeb2 100644 --- a/auth0-api-java/src/test/java/com/auth0/AllowedDPoPAuthenticationTest.java +++ b/auth0-api-java/src/test/java/com/auth0/AllowedDPoPAuthenticationTest.java @@ -7,8 +7,6 @@ import com.auth0.models.AuthToken; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.DPoPProofValidator; -import com.auth0.validators.JWTValidator; import org.junit.Before; import org.junit.Test; diff --git a/auth0-api-java/src/test/java/com/auth0/validators/ClaimValidatorTest.java b/auth0-api-java/src/test/java/com/auth0/ClaimValidatorTest.java similarity index 99% rename from auth0-api-java/src/test/java/com/auth0/validators/ClaimValidatorTest.java rename to auth0-api-java/src/test/java/com/auth0/ClaimValidatorTest.java index 5d2bd70..2b0c14d 100644 --- a/auth0-api-java/src/test/java/com/auth0/validators/ClaimValidatorTest.java +++ b/auth0-api-java/src/test/java/com/auth0/ClaimValidatorTest.java @@ -1,4 +1,4 @@ -package com.auth0.validators; +package com.auth0; import com.auth0.exception.BaseAuthException; import com.auth0.exception.InsufficientScopeException; diff --git a/auth0-api-java/src/test/java/com/auth0/validators/DPoPProofValidatorTest.java b/auth0-api-java/src/test/java/com/auth0/DPoPProofValidatorTest.java similarity index 99% rename from auth0-api-java/src/test/java/com/auth0/validators/DPoPProofValidatorTest.java rename to auth0-api-java/src/test/java/com/auth0/DPoPProofValidatorTest.java index e062616..7d6a85f 100644 --- a/auth0-api-java/src/test/java/com/auth0/validators/DPoPProofValidatorTest.java +++ b/auth0-api-java/src/test/java/com/auth0/DPoPProofValidatorTest.java @@ -1,4 +1,4 @@ -package com.auth0.validators; +package com.auth0; import com.auth0.exception.*; import com.auth0.jwt.JWT; diff --git a/auth0-api-java/src/test/java/com/auth0/DisabledDPoPAuthenticationTest.java b/auth0-api-java/src/test/java/com/auth0/DisabledDPoPAuthenticationTest.java index 88d75a0..f9b226a 100644 --- a/auth0-api-java/src/test/java/com/auth0/DisabledDPoPAuthenticationTest.java +++ b/auth0-api-java/src/test/java/com/auth0/DisabledDPoPAuthenticationTest.java @@ -5,7 +5,6 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.JWTValidator; import org.junit.Before; import org.junit.Test; diff --git a/auth0-api-java/src/test/java/com/auth0/validators/JWTValidatorTest.java b/auth0-api-java/src/test/java/com/auth0/JWTValidatorTest.java similarity index 99% rename from auth0-api-java/src/test/java/com/auth0/validators/JWTValidatorTest.java rename to auth0-api-java/src/test/java/com/auth0/JWTValidatorTest.java index 38653d7..77e18c3 100644 --- a/auth0-api-java/src/test/java/com/auth0/validators/JWTValidatorTest.java +++ b/auth0-api-java/src/test/java/com/auth0/JWTValidatorTest.java @@ -1,6 +1,5 @@ -package com.auth0.validators; +package com.auth0; -import com.auth0.cache.InMemoryAuthCache; import com.auth0.exception.InsufficientScopeException; import com.auth0.exception.InvalidRequestException; import com.auth0.exception.MissingRequiredArgumentException; diff --git a/auth0-api-java/src/test/java/com/auth0/validators/OidcDiscoveryFetcherTest.java b/auth0-api-java/src/test/java/com/auth0/OidcDiscoveryFetcherTest.java similarity index 98% rename from auth0-api-java/src/test/java/com/auth0/validators/OidcDiscoveryFetcherTest.java rename to auth0-api-java/src/test/java/com/auth0/OidcDiscoveryFetcherTest.java index bc041c4..27c6000 100644 --- a/auth0-api-java/src/test/java/com/auth0/validators/OidcDiscoveryFetcherTest.java +++ b/auth0-api-java/src/test/java/com/auth0/OidcDiscoveryFetcherTest.java @@ -1,6 +1,5 @@ -package com.auth0.validators; +package com.auth0; -import com.auth0.cache.InMemoryAuthCache; import com.auth0.exception.VerifyAccessTokenException; import com.auth0.models.OidcMetadata; import org.apache.http.HttpVersion; diff --git a/auth0-api-java/src/test/java/com/auth0/RequiredDPoPAuthenticationTest.java b/auth0-api-java/src/test/java/com/auth0/RequiredDPoPAuthenticationTest.java index aed1ea2..b328563 100644 --- a/auth0-api-java/src/test/java/com/auth0/RequiredDPoPAuthenticationTest.java +++ b/auth0-api-java/src/test/java/com/auth0/RequiredDPoPAuthenticationTest.java @@ -6,8 +6,6 @@ import com.auth0.models.AuthToken; import com.auth0.models.AuthenticationContext; import com.auth0.models.HttpRequestInfo; -import com.auth0.validators.DPoPProofValidator; -import com.auth0.validators.JWTValidator; import org.junit.Before; import org.junit.Test; diff --git a/auth0-api-java/src/test/java/com/auth0/cache/InMemoryAuthCacheTest.java b/auth0-api-java/src/test/java/com/auth0/cache/InMemoryAuthCacheTest.java index 5c052ed..89b13c4 100644 --- a/auth0-api-java/src/test/java/com/auth0/cache/InMemoryAuthCacheTest.java +++ b/auth0-api-java/src/test/java/com/auth0/cache/InMemoryAuthCacheTest.java @@ -1,5 +1,6 @@ package com.auth0.cache; +import com.auth0.InMemoryAuthCache; import org.junit.Before; import org.junit.Test; diff --git a/auth0-api-java/src/test/java/com/auth0/models/AuthOptionsTest.java b/auth0-api-java/src/test/java/com/auth0/models/AuthOptionsTest.java index 2f44323..b28ddf8 100644 --- a/auth0-api-java/src/test/java/com/auth0/models/AuthOptionsTest.java +++ b/auth0-api-java/src/test/java/com/auth0/models/AuthOptionsTest.java @@ -1,8 +1,8 @@ package com.auth0.models; import com.auth0.DomainResolver; -import com.auth0.cache.AuthCache; -import com.auth0.cache.InMemoryAuthCache; +import com.auth0.AuthCache; +import com.auth0.InMemoryAuthCache; import com.auth0.enums.DPoPMode; import org.junit.Test; diff --git a/auth0-springboot-api-playground/build.gradle b/auth0-springboot-api-playground/build.gradle index 135ff1f..9f8561b 100644 --- a/auth0-springboot-api-playground/build.gradle +++ b/auth0-springboot-api-playground/build.gradle @@ -16,10 +16,16 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' + + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' } test { useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } } tasks.named('bootJar') { diff --git a/auth0-springboot-api-playground/src/main/java/com/auth0/playground/McdDomainResolverExample.java b/auth0-springboot-api-playground/src/main/java/com/auth0/playground/McdDomainResolverExample.java index fd0baf1..0ddf36d 100644 --- a/auth0-springboot-api-playground/src/main/java/com/auth0/playground/McdDomainResolverExample.java +++ b/auth0-springboot-api-playground/src/main/java/com/auth0/playground/McdDomainResolverExample.java @@ -1,7 +1,7 @@ package com.auth0.playground; -import com.auth0.spring.boot.Auth0DomainResolver; -import com.auth0.spring.boot.Auth0RequestContext; +import com.auth0.DomainResolver; +import com.auth0.models.RequestContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,20 +14,17 @@ * Example: Multi-Custom Domain (MCD) configuration with a dynamic domain * resolver. *

- * Demonstrates how an end developer uses {@link Auth0DomainResolver} to - * dynamically - * resolve allowed issuer domains at request time — without any direct - * dependency - * on the core {@code auth0-api-java} module. + * Demonstrates how an end developer uses {@link DomainResolver} to dynamically + * resolve allowed issuer domains at request time. *

* *

How it works

*
    - *
  1. Define an {@link Auth0DomainResolver} bean in a {@code @Configuration} + *
  2. Define a {@link DomainResolver} bean in a {@code @Configuration} * class
  3. - *
  4. The auto-configuration picks it up and bridges it into the SDK's - * internal domain resolution pipeline
  5. - *
  6. On each request, the resolver receives an {@link Auth0RequestContext} + *
  7. The auto-configuration picks it up and passes it to the SDK's + * domain resolution pipeline
  8. + *
  9. On each request, the resolver receives a {@link RequestContext} * containing the request URL, headers, and unverified token issuer
  10. *
  11. The resolver returns the list of allowed issuer domains for that * request
  12. @@ -36,7 +33,7 @@ *

    Activation

    *

    * Just define this {@code @Configuration} class in your project. - * The auto-configuration detects the {@link Auth0DomainResolver} bean + * The auto-configuration detects the {@link DomainResolver} bean * automatically — no extra YAML properties needed. *

    * @@ -50,8 +47,8 @@ * against a known allowlist * * - * @see Auth0DomainResolver - * @see Auth0RequestContext + * @see DomainResolver + * @see RequestContext */ @Configuration public class McdDomainResolverExample { @@ -72,7 +69,7 @@ public class McdDomainResolverExample { * Dynamic domain resolver that resolves allowed issuers based on the * {@code X-Tenant-ID} request header. *

    - * The resolver receives an {@link Auth0RequestContext} with: + * The resolver receives a {@link RequestContext} with: *

      *
    • {@code context.getUrl()} — the API request URL
    • *
    • {@code context.getHeaders()} — all request headers (lowercase keys)
    • @@ -88,10 +85,10 @@ public class McdDomainResolverExample { * http://localhost:8080/api/protected * * - * @return an {@link Auth0DomainResolver} that maps tenant IDs to Auth0 domains + * @return a {@link DomainResolver} that maps tenant IDs to Auth0 domains */ @Bean - public Auth0DomainResolver domainResolver() { + public DomainResolver domainResolver() { return context -> { String tenantId = context.getHeaders().get("x-tenant-id"); diff --git a/auth0-springboot-api-playground/src/main/java/com/auth0/playground/ProfileController.java b/auth0-springboot-api-playground/src/main/java/com/auth0/playground/ProfileController.java index f405a4a..6d3c773 100644 --- a/auth0-springboot-api-playground/src/main/java/com/auth0/playground/ProfileController.java +++ b/auth0-springboot-api-playground/src/main/java/com/auth0/playground/ProfileController.java @@ -30,7 +30,7 @@ public Map pub() { * demonstrates that the same controller works seamlessly with * Multi-Custom Domain configurations. *

      - * When an {@link com.auth0.spring.boot.Auth0DomainResolver} bean is + * When a {@link com.auth0.DomainResolver} bean is * defined, the SDK resolves the allowed issuer domains dynamically. * This endpoint does not need any MCD-specific code. *

      diff --git a/auth0-springboot-api-playground/src/main/java/com/auth0/playground/RedisCacheExample.java b/auth0-springboot-api-playground/src/main/java/com/auth0/playground/RedisCacheExample.java new file mode 100644 index 0000000..477d4b2 --- /dev/null +++ b/auth0-springboot-api-playground/src/main/java/com/auth0/playground/RedisCacheExample.java @@ -0,0 +1,154 @@ +package com.auth0.playground; + +import com.auth0.AuthCache; + +/** + * Example: Using Redis as a distributed cache for OIDC discovery metadata and JWKS. + *

      + * The SDK uses a unified cache with key prefixes: + *

        + *
      • {@code discovery:{issuerUrl}} — OIDC discovery metadata
      • + *
      • {@code jwks:{jwksUri}} — JWKS provider instances
      • + *
      + *

      + * By implementing {@link AuthCache}, you can replace the default in-memory LRU + * cache with any distributed backend (Redis, Memcached, etc.) without changing + * any SDK internals. + * + *

      When to use a distributed cache

      + *
        + *
      • Multi-instance deployments where each node shouldn't fetch OIDC/JWKS independently
      • + *
      • Reducing cold-start latency after deployments
      • + *
      • Centralised cache invalidation across all API instances
      • + *
      + * + *

      Spring Boot usage

      + *

      + * Just define an {@link AuthCache} bean — the auto-configuration picks it up + * automatically and wires it into {@code AuthOptions}. No need to create your + * own {@code AuthClient} bean. When an {@code AuthCache} bean is present, + * the {@code cacheMaxEntries} and {@code cacheTtlSeconds} YAML properties + * are ignored. + *

      + *
      {@code
      + * @Configuration
      + * public class CacheConfig {
      + *     @Bean
      + *     public AuthCache authCache(RedisTemplate redisTemplate) {
      + *         return new RedisAuthCache(redisTemplate, 600);
      + *     }
      + * }
      + * }
      + *
      + * 

      Important notes

      + *
        + *
      • The cache stores mixed value types (OidcMetadata, JwkProvider) — the Redis + * serializer must handle this (e.g., Java serialization or a type-aware JSON strategy)
      • + *
      • Implementations must be thread-safe
      • + *
      • {@code get()} must return {@code null} for missing or expired keys — never throw
      • + *
      + */ +public class RedisCacheExample { + + /** + * Example Redis-backed implementation of {@link AuthCache}. + *

      + * This is a reference implementation showing how to adapt a Redis client + * to the SDK's cache interface. Replace the placeholder Redis operations + * with your actual Redis client (Jedis, Lettuce, Spring RedisTemplate, etc.). + */ + public static class RedisAuthCache implements AuthCache { + + // Replace with your actual Redis client + // e.g., private final RedisTemplate redisTemplate; + // e.g., private final JedisPool jedisPool; + // e.g., private final RedisClient lettuceClient; + + private final long ttlSeconds; + private final String keyPrefix; + + /** + * Creates a Redis-backed auth cache. + * + * @param ttlSeconds time-to-live for cached entries in seconds + */ + public RedisAuthCache(long ttlSeconds) { + this(ttlSeconds, "auth0:"); + } + + /** + * Creates a Redis-backed auth cache with a custom key prefix. + * + * @param ttlSeconds time-to-live for cached entries in seconds + * @param keyPrefix prefix for all Redis keys (e.g., "auth0:" to namespace) + */ + public RedisAuthCache(long ttlSeconds, String keyPrefix) { + this.ttlSeconds = ttlSeconds; + this.keyPrefix = keyPrefix; + } + + @Override + public Object get(String key) { + // Example with Spring RedisTemplate: + // return redisTemplate.opsForValue().get(keyPrefix + key); + // + // Example with Jedis: + // try (Jedis jedis = jedisPool.getResource()) { + // byte[] data = jedis.get((keyPrefix + key).getBytes()); + // return data != null ? deserialize(data) : null; + // } + // + // Must return null for missing/expired keys — never throw + return null; + } + + @Override + public void put(String key, Object value) { + // Example with Spring RedisTemplate: + // redisTemplate.opsForValue().set(keyPrefix + key, value, Duration.ofSeconds(ttlSeconds)); + // + // Example with Jedis: + // try (Jedis jedis = jedisPool.getResource()) { + // jedis.setex((keyPrefix + key).getBytes(), ttlSeconds, serialize(value)); + // } + // + // The SDK stores both OIDC metadata and JWKS providers using + // prefixed keys like "discovery:https://tenant.auth0.com/" + // and "jwks:https://tenant.auth0.com/.well-known/jwks.json" + } + + @Override + public void remove(String key) { + // Example with Spring RedisTemplate: + // redisTemplate.delete(keyPrefix + key); + // + // Example with Jedis: + // try (Jedis jedis = jedisPool.getResource()) { + // jedis.del((keyPrefix + key).getBytes()); + // } + } + + @Override + public void clear() { + // Example with Spring RedisTemplate (scan + delete): + // Set keys = redisTemplate.keys(keyPrefix + "*"); + // if (keys != null && !keys.isEmpty()) { + // redisTemplate.delete(keys); + // } + // + // WARNING: KEYS command is expensive in production. + // Consider using SCAN or maintaining a key set for bulk deletion. + } + + @Override + public int size() { + // Example with Spring RedisTemplate: + // Set keys = redisTemplate.keys(keyPrefix + "*"); + // return keys != null ? keys.size() : 0; + // + // This is an approximation — exact count may vary due to TTL expiry. + return 0; + } + } + +} diff --git a/auth0-springboot-api/build.gradle b/auth0-springboot-api/build.gradle index 87fa857..21abbb4 100644 --- a/auth0-springboot-api/build.gradle +++ b/auth0-springboot-api/build.gradle @@ -22,7 +22,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' - implementation project(':auth0-api-java') + api project(':auth0-api-java') implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'org.apache.httpcomponents:httpclient:4.5.14' diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java index 04346f9..f60505f 100644 --- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java +++ b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java @@ -1,5 +1,6 @@ package com.auth0.spring.boot; +import com.auth0.AuthCache; import com.auth0.AuthClient; import com.auth0.DomainResolver; import com.auth0.models.AuthOptions; @@ -16,18 +17,18 @@ *
        *
      1. Single domain — set {@code auth0.domain} in YAML
      2. *
      3. Static MCD list — set {@code auth0.domains} in YAML
      4. - *
      5. Dynamic resolver — define an {@link Auth0DomainResolver} bean
      6. + *
      7. Dynamic resolver — define a {@link DomainResolver} bean
      8. *
      * * Dynamic Domain Resolver *

      * To dynamically resolve allowed issuer domains at request time, define a bean - * implementing {@link Auth0DomainResolver}: + * implementing {@link DomainResolver}: *

      - * + * *
      {@code
        * @Bean
      - * public Auth0DomainResolver domainResolver() {
      + * public DomainResolver domainResolver() {
        *     return context -> {
        *         String tenantId = context.getHeaders().get("x-tenant-id");
        *         return lookupDomainsForTenant(tenantId);
      @@ -43,35 +44,33 @@ public class Auth0AutoConfiguration {
            * Creates an {@link AuthOptions} bean from {@link Auth0Properties}.
            * 

      * Builds the authentication options configuration. - * + * * @param properties the Auth0 configuration properties from * application configuration - * @param domainResolverProvider optional {@link Auth0DomainResolver} bean + * @param domainResolverProvider optional {@link DomainResolver} bean * for dynamic MCD resolution. When present, * it takes precedence over static YAML config. + * @param cacheProvider optional {@link AuthCache} bean for custom + * caching (e.g., Redis). When present, + * {@code cacheMaxEntries} and {@code cacheTtlSeconds} + * properties are ignored. * @return configured AuthOptions instance for creating AuthClient * @see AuthOptions.Builder * @see Auth0Properties */ @Bean public AuthOptions authOptions(Auth0Properties properties, - ObjectProvider domainResolverProvider) { + ObjectProvider domainResolverProvider, + ObjectProvider> cacheProvider) { - Auth0DomainResolver auth0DomainResolver = domainResolverProvider.getIfAvailable(); + DomainResolver domainResolver = domainResolverProvider.getIfAvailable(); + AuthCache cache = cacheProvider.getIfAvailable(); AuthOptions.Builder builder = new AuthOptions.Builder() .audience(properties.getAudience()); - if (auth0DomainResolver != null) { - // Bridge Spring Boot's Auth0DomainResolver → core DomainResolver - DomainResolver coreDomainResolver = coreContext -> { - Auth0RequestContext springContext = new Auth0RequestContext( - coreContext.getUrl(), - coreContext.getHeaders(), - coreContext.getTokenIssuer()); - return auth0DomainResolver.resolveDomains(springContext); - }; - builder.domainsResolver(coreDomainResolver); + if (domainResolver != null) { + builder.domainsResolver(domainResolver); if (properties.getDomain() != null && !properties.getDomain().isEmpty()) { builder.domain(properties.getDomain()); @@ -96,11 +95,15 @@ public AuthOptions authOptions(Auth0Properties properties, builder.dpopIatOffsetSeconds(properties.getDpopIatOffsetSeconds()); } - if (properties.getCacheMaxEntries() != null) { - builder.cacheMaxEntries(properties.getCacheMaxEntries()); - } - if (properties.getCacheTtlSeconds() != null) { - builder.cacheTtlSeconds(properties.getCacheTtlSeconds()); + if (cache != null) { + builder.cache(cache); + } else { + if (properties.getCacheMaxEntries() != null) { + builder.cacheMaxEntries(properties.getCacheMaxEntries()); + } + if (properties.getCacheTtlSeconds() != null) { + builder.cacheTtlSeconds(properties.getCacheTtlSeconds()); + } } return builder.build(); diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0DomainResolver.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0DomainResolver.java deleted file mode 100644 index de83413..0000000 --- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0DomainResolver.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.auth0.spring.boot; - -import java.util.List; - -/** - * Functional interface for dynamically resolving allowed issuer domains - * based on the incoming request context. - *

      - * Used in Multi-Custom Domain (MCD) scenarios where the set of valid issuers - * cannot be determined statically at configuration time. Define a Spring bean - * implementing this interface, and the auto-configuration will pick it up - * automatically. - *

      - * - * Example: Tenant-based resolution - * - *
      {@code
      - * @Bean
      - * public Auth0DomainResolver domainResolver(TenantService tenantService) {
      - *     return context -> {
      - *         String tenantId = context.getHeaders().get("x-tenant-id");
      - *         String domain = tenantService.getDomain(tenantId);
      - *         return Collections.singletonList(domain);
      - *     };
      - * }
      - * }
      - * - * Example: Issuer-hint based resolution - * - *
      {@code
      - * @Bean
      - * public Auth0DomainResolver domainResolver() {
      - *     return context -> {
      - *         // Use the unverified iss claim as a routing hint
      - *         String issuer = context.getTokenIssuer();
      - *         if (issuer != null && allowedIssuers.contains(issuer)) {
      - *             return Collections.singletonList(issuer);
      - *         }
      - *         return Collections.emptyList();
      - *     };
      - * }
      - * }
      - * - * Priority - *

      - * When an {@code Auth0DomainResolver} bean is present, it takes precedence - * over the static {@code auth0.domains} YAML list. The single - * {@code auth0.domain} - * can still coexist as a fallback. - *

      - * - * @see Auth0RequestContext - * @see Auth0Properties - */ -@FunctionalInterface -public interface Auth0DomainResolver { - - /** - * Resolves the list of allowed issuer domains for the given request context. - * - * @param context the request context containing URL, headers, and - * unverified token issuer - * @return a list of allowed issuer domain strings (e.g., - * {@code ["login.acme.com", "auth.partner.com"]}); - * may return {@code null} or an empty list if no domains can be - * resolved - */ - List resolveDomains(Auth0RequestContext context); -} diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0RequestContext.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0RequestContext.java deleted file mode 100644 index 6849d4b..0000000 --- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0RequestContext.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.auth0.spring.boot; - -import java.util.Collections; -import java.util.Map; - -/** - * Immutable request context passed to {@link Auth0DomainResolver} for dynamic - * domain resolution in Multi-Custom Domain (MCD) scenarios. - *

      - * Contains all the information a resolver needs to determine which issuer - * domains are valid for the incoming request: - *

        - *
      • {@code url} — the URL the API request was made to
      • - *
      • {@code headers} — relevant HTTP request headers (lowercase keys)
      • - *
      • {@code tokenIssuer} — the unverified {@code iss} claim from the - * JWT
      • - *
      - * - *

      - * Warning: The {@code tokenIssuer} has NOT been verified yet. It is - * provided as a routing hint only and must not be trusted on its own. - *

      - * - * @see Auth0DomainResolver - */ -public final class Auth0RequestContext { - - private final String url; - private final Map headers; - private final String tokenIssuer; - - /** - * Creates a new request context. - * - * @param url the request URL - * @param headers the request headers (will be wrapped as unmodifiable) - * @param tokenIssuer the unverified {@code iss} claim from the JWT - */ - public Auth0RequestContext(String url, Map headers, String tokenIssuer) { - this.url = url; - this.headers = headers != null - ? Collections.unmodifiableMap(headers) - : Collections.emptyMap(); - this.tokenIssuer = tokenIssuer; - } - - /** - * Returns the URL the API request was made to. - * - * @return the request URL, or {@code null} if not available - */ - public String getUrl() { - return url; - } - - /** - * Returns an unmodifiable map of request headers (lowercase keys). - * - * @return the request headers; never {@code null} - */ - public Map getHeaders() { - return headers; - } - - /** - * Returns the unverified {@code iss} claim from the incoming JWT. - *

      - * Warning: This value has NOT been verified. Use it only as a - * routing hint (e.g., to look up tenant configuration). - * - * @return the unverified issuer, or {@code null} if not available - */ - public String getTokenIssuer() { - return tokenIssuer; - } -} diff --git a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java index 17ed0c8..16646fe 100644 --- a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java +++ b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java @@ -1,6 +1,7 @@ package com.auth0.spring.boot; import com.auth0.AuthClient; +import com.auth0.DomainResolver; import com.auth0.models.AuthOptions; import com.auth0.enums.DPoPMode; import org.junit.jupiter.api.DisplayName; @@ -237,7 +238,7 @@ class DomainResolverBeanTest { @TestConfiguration static class TestConfig { @Bean - public Auth0DomainResolver testDomainResolver() { + public DomainResolver testDomainResolver() { return context -> { String tenant = context.getHeaders().get("x-tenant-id"); if ("acme".equals(tenant)) { @@ -252,7 +253,7 @@ public Auth0DomainResolver testDomainResolver() { private AuthOptions authOptions; @Test - @DisplayName("Should use Auth0DomainResolver bean when present, taking priority over domains list") + @DisplayName("Should use DomainResolver bean when present, taking priority over domains list") void shouldUseResolverBeanWhenPresent() { assertNotNull(authOptions); // When a resolver is present, domainsResolver should be set @@ -284,7 +285,7 @@ class ResolverPriorityOverDomainsTest { @TestConfiguration static class TestConfig { @Bean - public Auth0DomainResolver testDomainResolver() { + public DomainResolver testDomainResolver() { return context -> Collections.singletonList("dynamic.auth0.com"); } } @@ -293,7 +294,7 @@ public Auth0DomainResolver testDomainResolver() { private AuthOptions authOptions; @Test - @DisplayName("Should prioritize Auth0DomainResolver over static domains list") + @DisplayName("Should prioritize DomainResolver over static domains list") void shouldPrioritizeResolverOverStaticDomains() { assertNotNull(authOptions); // Resolver should win over static domains From 53a9f2a0c847e17028968db52b5ea2c8ae6d1b2a Mon Sep 17 00:00:00 2001 From: tanya732 Date: Thu, 26 Feb 2026 10:50:41 +0530 Subject: [PATCH 2/2] Updated Readme --- README.md | 4 +- auth0-springboot-api/README.md | 142 +++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a95c31..b828a5e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ![Java Version](https://img.shields.io/badge/java-8%2B-blue) ![License](https://img.shields.io/badge/license-MIT-green) -A comprehensive Java library for Auth0 JWT authentication with built-in **DPoP (Demonstration of Proof-of-Possession)** support. This multi-module project provides both a core authentication library and Spring Boot integration for secure API development. +A comprehensive Java library for Auth0 JWT authentication with built-in **DPoP (Demonstration of Proof-of-Possession)** and **Multi-Custom Domain (MCD)** support. This project provides both a core authentication library and Spring Boot integration for secure API development. ## 🏗️ Architecture Overview @@ -49,6 +49,8 @@ The core library (`auth0-api-java`) is currently an internal module used by the - JWT validation with Auth0 JWKS integration - DPoP proof validation per [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449) +- Multi-Custom Domain (MCD) support — static domain lists, or dynamic resolution at request time +- Extensible caching — pluggable `AuthCache` interface for distributed backends (Redis, Memcached) - Flexible authentication strategies diff --git a/auth0-springboot-api/README.md b/auth0-springboot-api/README.md index a7f470c..c9e68ca 100644 --- a/auth0-springboot-api/README.md +++ b/auth0-springboot-api/README.md @@ -6,6 +6,8 @@ This library builds on top of the standard Spring Security JWT authentication, p - **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](https://datatracker.ietf.org/doc/html/rfc9449) +- **Multi-Custom Domain (MCD) Support** - Validate tokens from multiple Auth0 custom domains with static lists or dynamic resolution +- **Extensible Caching** - Pluggable `AuthCache` interface 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 @@ -174,6 +176,146 @@ curl -H "Authorization: DPoP " \ https://your-api.example.com/api/protected ``` +## Multi-Custom Domain (MCD) Support + +For tenants with multiple custom domains, the SDK can validate tokens from any of the configured issuers. There are three ways to configure domain resolution: + +### Option 1: Static Domain List + +Configure a list of allowed issuer domains in `application.yml`: + +```yaml +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: + +```yaml +auth0: + domain: "primary.auth0.com" + audience: "https://your-api-identifier" + domains: + - "login.acme.com" + - "auth.partner.com" +``` + +### Option 2: Dynamic Domain Resolver + +For scenarios where the allowed issuers depend on runtime context (e.g., tenant headers, database lookups), define a `DomainResolver` bean: + +```java +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. + +### Option 3: Single Domain (Default) + +For single-tenant setups, just use the `auth0.domain` property: + +```yaml +auth0: + domain: "your-tenant.auth0.com" + audience: "https://your-api-identifier" +``` + +## Extensibility + +### Custom Cache Implementation + +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: + +```java +import com.auth0.AuthCache; + +public class RedisAuthCache implements AuthCache { + + private final RedisTemplate redisTemplate; + private final Duration ttl; + + public RedisAuthCache(RedisTemplate 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 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: + +```java +@Configuration +public class CacheConfig { + + @Bean + public AuthCache authCache(RedisTemplate 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. + +### Default Cache Settings + +If no custom cache is provided, the built-in in-memory cache is used with these defaults: + +```yaml +auth0: + cacheMaxEntries: 100 # max entries before LRU eviction + cacheTtlSeconds: 600 # 10-minute TTL per entry +``` + ## Advanced Features ### Manual JWT Validation