Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 project provides 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 Spring Boot integration for secure API development.

## 🏗️ Architecture Overview

Expand Down Expand Up @@ -49,6 +49,8 @@ It provides:

- 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


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,21 +26,21 @@ protected AbstractAuthentication(JWTValidator jwtValidator, TokenExtractor extra
/**
* Concrete method to validate Bearer token headers and JWT claims.
*/
protected DecodedJWT validateBearerToken(Map<String, String> headers, HttpRequestInfo httpRequestInfo) throws BaseAuthException {
AuthToken authToken = extractor.extractBearer(headers);
return jwtValidator.validateToken(authToken.getAccessToken(), headers, httpRequestInfo);
protected DecodedJWT validateBearerToken(HttpRequestInfo httpRequestInfo) throws BaseAuthException {
AuthToken authToken = extractor.extractBearer(httpRequestInfo.getHeaders());
return jwtValidator.validateToken(authToken.getAccessToken(), httpRequestInfo);
}

/**
* Concrete method to validate DPoP token headers, JWT claims, and proof.
*/
protected DecodedJWT validateDpopTokenAndProof(Map<String, String> headers, HttpRequestInfo requestInfo)
protected DecodedJWT validateDpopTokenAndProof(HttpRequestInfo requestInfo)
throws BaseAuthException {

AuthValidatorHelper.validateHttpMethodAndHttpUrl(requestInfo);

AuthToken authToken = extractor.extractDPoPProofAndDPoPToken(headers);
DecodedJWT decodedJwtToken = jwtValidator.validateToken(authToken.getAccessToken(), headers, requestInfo);
AuthToken authToken = extractor.extractDPoPProofAndDPoPToken(requestInfo.getHeaders());
DecodedJWT decodedJwtToken = jwtValidator.validateToken(authToken.getAccessToken(), requestInfo);

dpopProofValidator.validate(authToken.getProof(), decodedJwtToken, requestInfo);

Expand All @@ -52,9 +50,7 @@ protected DecodedJWT validateDpopTokenAndProof(Map<String, String> headers, Http
/**
* Main abstract method for each concrete strategy.
*/
public abstract AuthenticationContext authenticate(
Map<String, String> headers,
HttpRequestInfo requestInfo
public abstract AuthenticationContext authenticate(HttpRequestInfo requestInfo
) throws BaseAuthException;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,30 +17,27 @@ public AllowedDPoPAuthentication(JWTValidator jwtValidator,

/**
* Authenticates the request when DPoP Mode is Allowed (Accepts both DPoP and Bearer tokens) .
* @param headers request headers
* @param requestInfo HTTP request info
* @return AuthenticationContext with JWT claims
* @throws BaseAuthException if validation fails
*/
@Override
public AuthenticationContext authenticate(Map<String, String> headers, HttpRequestInfo requestInfo)
public AuthenticationContext authenticate(HttpRequestInfo requestInfo)
throws BaseAuthException {

String scheme = "";

try{
Map<String, String> normalizedHeader = normalize(headers);

scheme = extractor.getScheme(normalizedHeader);
scheme = extractor.getScheme(requestInfo.getHeaders());

if (scheme.equalsIgnoreCase(AuthConstants.BEARER_SCHEME)) {
DecodedJWT jwtToken = validateBearerToken(normalizedHeader, requestInfo);
AuthValidatorHelper.validateNoDpopPresence(normalizedHeader, jwtToken);
DecodedJWT jwtToken = validateBearerToken(requestInfo);
AuthValidatorHelper.validateNoDpopPresence(requestInfo.getHeaders(), jwtToken);
return buildContext(jwtToken);
}

if (scheme.equalsIgnoreCase(AuthConstants.DPOP_SCHEME)) {
DecodedJWT decodedJWT = validateDpopTokenAndProof(normalizedHeader, requestInfo);
DecodedJWT decodedJWT = validateDpopTokenAndProof(requestInfo);
return buildContext(decodedJWT);
}

Expand Down
65 changes: 65 additions & 0 deletions auth0-api-java/src/main/java/com/auth0/AuthCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.auth0;

/**
* Cache abstraction for storing authentication-related data such as
* OIDC discovery metadata and JWKS providers.
* <p>
* The SDK ships with a default in-memory LRU implementation
* ({@link InMemoryAuthCache}). Users can implement this interface
* to plug in distributed cache backends (e.g., Redis, Memcached) without
* breaking changes to the SDK's public API.
* </p>
*
* <p>
* A single {@code AuthCache<Object>} instance can serve as a unified cache
* for both discovery metadata and JWKS providers by using key prefixes:
* </p>
* <ul>
* <li>{@code discovery:{issuerUrl}} — OIDC discovery metadata</li>
* <li>{@code jwks:{jwksUri}} — JwkProvider instances</li>
* </ul>
*
* <h3>Thread Safety</h3>
* <p>
* All implementations <b>must</b> be thread-safe.
* </p>
*
* @param <V> the type of cached values
*/
public interface AuthCache<V> {

/**
* Retrieves a value from the cache.
*
* @param key the cache key
* @return the cached value, or {@code null} if not present or expired
*/
V get(String key);

/**
* Stores a value in the cache with the cache's default TTL.
*
* @param key the cache key
* @param value the value to cache
*/
void put(String key, V value);

/**
* Removes a specific entry from the cache.
*
* @param key the cache key to remove
*/
void remove(String key);

/**
* Removes all entries from the cache.
*/
void clear();

/**
* Returns the number of entries currently in the cache.
*
* @return the cache size
*/
int size();
}
9 changes: 2 additions & 7 deletions auth0-api-java/src/main/java/com/auth0/AuthClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -45,12 +41,11 @@ public static AuthClient from(AuthOptions options) {

/**
* Verifies the incoming request headers and HTTP request info.
* @param headers request headers
* @param requestInfo HTTP request info
* @return AuthenticationContext with JWT claims
* @throws BaseAuthException if verification fails
*/
public AuthenticationContext verifyRequest(Map<String, String> headers, HttpRequestInfo requestInfo) throws BaseAuthException {
return orchestrator.process(headers, requestInfo);
public AuthenticationContext verifyRequest(HttpRequestInfo requestInfo) throws BaseAuthException {
return orchestrator.process(requestInfo);
}
}
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public AuthenticationOrchestrator(AbstractAuthentication authStrategy) {
this.authStrategy = authStrategy;
}

public AuthenticationContext process(Map<String, String> headers, HttpRequestInfo requestInfo)
public AuthenticationContext process(HttpRequestInfo requestInfo)
throws BaseAuthException {
return authStrategy.authenticate(headers, requestInfo);
return authStrategy.authenticate(requestInfo);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package com.auth0.validators;
package com.auth0;

import com.auth0.exception.*;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.*;

/**
* Utility class for JWT claim validation
*
* Provides functionality to validate JWT claims including scopes and custom
* claim checks.
* Utility class for JWT claim validation. Provides functionality to validate JWT claims including scopes and custom claim checks.
* This is the Java equivalent of the TypeScript claim validation utilities.
*/
class ClaimValidator {
Expand All @@ -27,13 +24,11 @@ static Set<String> getClaimValues(DecodedJWT jwt, String claimName) throws BaseA
throw new VerifyAccessTokenException("Required claim is missing");
}

// Case 1: space-separated string
String strValue = jwt.getClaim(claimName).asString();
if (strValue != null) {
return new HashSet<>(Arrays.asList(strValue.trim().split("\\s+")));
}

// Case 2: list of strings
List<String> listValue = jwt.getClaim(claimName).asList(String.class);
if (listValue != null) {
return new HashSet<>(listValue);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.auth0.validators;
package com.auth0;

import com.auth0.exception.BaseAuthException;
import com.auth0.exception.InvalidDpopProofException;
Expand All @@ -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;
}

Expand All @@ -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);
Expand Down Expand Up @@ -197,7 +197,7 @@ String calculateJwkThumbprint(Map<String, Object> jwk) throws BaseAuthException
}
}

public static ECPublicKey convertJwkToEcPublicKey(Map<String, Object> jwkMap)
static ECPublicKey convertJwkToEcPublicKey(Map<String, Object> jwkMap)
throws JwkException {

Jwk jwk = Jwk.fromValues(jwkMap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -17,18 +14,16 @@ public DisabledDPoPAuthentication(JWTValidator jwtValidator, TokenExtractor extr

/**
* Authenticates the request when DPoP Mode is Disabled (Accepts only Bearer tokens) .
* @param headers request headers
* @param requestInfo HTTP request info
* @return AuthenticationContext with JWT claims
* @throws BaseAuthException if validation fails
*/
@Override
public AuthenticationContext authenticate(Map<String, String> headers, HttpRequestInfo requestInfo)
public AuthenticationContext authenticate(HttpRequestInfo requestInfo)
throws BaseAuthException {

Map<String, String> normalizedHeader = normalize(headers);
try {
DecodedJWT jwt = validateBearerToken(normalizedHeader, requestInfo);
DecodedJWT jwt = validateBearerToken(requestInfo);

return buildContext(jwt);
} catch (BaseAuthException ex){
Expand Down
44 changes: 44 additions & 0 deletions auth0-api-java/src/main/java/com/auth0/DomainResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.auth0;

import com.auth0.models.RequestContext;

import java.util.List;

/**
* Functional interface for dynamically resolving allowed issuer domains
* based on the incoming request context.
* <p>
* Used in multi-custom-domain (MCD) scenarios where the set of valid issuers
* cannot be determined statically at configuration time. The resolver receives
* a {@link RequestContext} containing the request URL, headers, and the
* unverified token issuer, and returns the list of allowed issuer domains.
* </p>
*
* <pre>{@code
* AuthOptions options = new AuthOptions.Builder()
* .domainsResolver(context -> {
* String host = context.getHeaders().get("host");
* return lookupIssuersForHost(host);
* })
* .audience("https://api.example.com")
* .build();
* }</pre>
*
* @see RequestContext
* @see com.auth0.models.AuthOptions.Builder#domainsResolver(DomainResolver)
*/
@FunctionalInterface
public interface DomainResolver {

/**
* 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 ["https://tenant1.auth0.com/"]});
* may return {@code null} or an empty list if no domains can be
* resolved
*/
List<String> resolveDomains(RequestContext context);
}
Loading
Loading