Skip to content

Feat: Added MCD Support#199

Merged
tanya732 merged 11 commits intomasterfrom
feat/mcd-support
Apr 9, 2026
Merged

Feat: Added MCD Support#199
tanya732 merged 11 commits intomasterfrom
feat/mcd-support

Conversation

@tanya732
Copy link
Copy Markdown
Contributor

@tanya732 tanya732 commented Jan 27, 2026

Summary

  • Adds Multiple Custom Domain (MCD) support, enabling a single SDK instance to authenticate against multiple Auth0 custom domains based on per-request context (subdomain, header, etc.)
  • Introduces DomainResolver interface for dynamic domain resolution, alongside the existing static domain mode
  • Secures the resolved domain across the OAuth redirect using HMAC-signed cookies (via SignedCookieUtils), preventing tampering between the authorize and callback phases
  • Enriches the Tokens object with domain and issuer fields so callers know which tenant issued the tokens
  • Fixes a thread-safety issue in RequestProcessor where shared mutable IdTokenVerifier.Options could cause incorrect nonce validation under concurrent requests, verification options are now created per-request

New Public API

Addition Description
DomainResolver interface String resolve(HttpServletRequest) user-provided function to resolve Auth0 domain per request
AuthenticationController.newBuilder(DomainResolver, clientId, clientSecret) Factory method for MCD mode
Tokens.getDomain() / Tokens.getIssuer() Access which tenant domain/issuer the tokens were obtained from

Internal Changes

Component Change
RequestProcessor Refactored from single pre-built AuthAPI client to per-request client creation via DomainProvider abstraction. Verification options now created per-request for thread safety.
AuthorizeUrl Stores HMAC-signed origin_domain cookie during build() / fromPushedAuthorizationRequest()`
TransientCookieStore New storeSignedOriginDomain() / getSignedOriginDomain() methods for HMAC cookie lifecycle
SignedCookieUtils (new) HMAC-SHA256 signing and verification utility using client secret
IdTokenVerifier.Options Issuer field is now mutable; new constructor without issuer for deferred resolution
DomainProvider / StaticDomainProvider / ResolverDomainProvider Package-private abstraction unifying static and dynamic domain modes

Backward Compatibility

  • No breaking changes — existing newBuilder(domain, clientId, clientSecret) continues to work unchanged
  • Existing static-domain applications require zero code changes

Testing

  • Added SignedCookieUtilsTest — HMAC signing, verification, and tampering detection
  • Updated AuthenticationControllerTest — covers static domain, resolver-based domain, builder validation (mutual exclusivity of domain/resolver), cookie path, organization, invitation
  • Updated RequestProcessorTest — per-request client creation, domain resolution, HMAC cookie round-trip, token enrichment with domain/issuer, state/nonce validation across both storage modes
  • Updated TransientCookieStoreTest — signed origin domain cookie storage and retrieval
  • Updated TokensTest — new domain/issuer fields

return new Tokens(request.getParameter(KEY_ACCESS_TOKEN), request.getParameter(KEY_ID_TOKEN), null, request.getParameter(KEY_TOKEN_TYPE), expiresIn);
private Tokens getFrontChannelTokens(HttpServletRequest request, String originDomain, String originIssuer) {
Long expiresIn = request.getParameter(KEY_EXPIRES_IN) == null ? null
: Long.parseLong(request.getParameter(KEY_EXPIRES_IN));

Check notice

Code scanning / CodeQL

Missing catch of NumberFormatException Note

Potential uncaught 'java.lang.NumberFormatException'.

Copilot Autofix

AI 24 days ago

In general, the problem is fixed by surrounding the numeric parsing operation with a try/catch for NumberFormatException, or by validating that the string is numeric before parsing, and then deciding how to handle invalid input (e.g., logging, defaulting to null, or rejecting the request). For this specific method, the best non-invasive behavior is: keep returning the same expiresIn value when parsing succeeds, and when parsing fails, treat it as if no expires_in was provided (i.e., set expiresIn to null), which avoids throwing while not changing the behavior of valid requests.

Concretely, in src/main/java/com/auth0/RequestProcessor.java, within getFrontChannelTokens, we should replace the direct ternary call to Long.parseLong(...) with a small block that (1) reads the parameter into a local String, (2) attempts to parse it inside a try block, (3) catches NumberFormatException and sets expiresIn to null. No new imports are needed because NumberFormatException is in java.lang. The rest of the method remains unchanged, still passing expiresIn into the Tokens constructor.

Suggested changeset 1
src/main/java/com/auth0/RequestProcessor.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/com/auth0/RequestProcessor.java b/src/main/java/com/auth0/RequestProcessor.java
--- a/src/main/java/com/auth0/RequestProcessor.java
+++ b/src/main/java/com/auth0/RequestProcessor.java
@@ -404,8 +404,16 @@
      *         parameters.
      */
     private Tokens getFrontChannelTokens(HttpServletRequest request, String originDomain, String originIssuer) {
-        Long expiresIn = request.getParameter(KEY_EXPIRES_IN) == null ? null
-                : Long.parseLong(request.getParameter(KEY_EXPIRES_IN));
+        String expiresInParam = request.getParameter(KEY_EXPIRES_IN);
+        Long expiresIn = null;
+        if (expiresInParam != null) {
+            try {
+                expiresIn = Long.parseLong(expiresInParam);
+            } catch (NumberFormatException ignored) {
+                // If the expires_in parameter is not a valid number, ignore it and treat as unspecified.
+                expiresIn = null;
+            }
+        }
         return new Tokens(request.getParameter(KEY_ACCESS_TOKEN), request.getParameter(KEY_ID_TOKEN), null,
                 request.getParameter(KEY_TOKEN_TYPE), expiresIn, originDomain, originIssuer);
     }
EOF
@@ -404,8 +404,16 @@
* parameters.
*/
private Tokens getFrontChannelTokens(HttpServletRequest request, String originDomain, String originIssuer) {
Long expiresIn = request.getParameter(KEY_EXPIRES_IN) == null ? null
: Long.parseLong(request.getParameter(KEY_EXPIRES_IN));
String expiresInParam = request.getParameter(KEY_EXPIRES_IN);
Long expiresIn = null;
if (expiresInParam != null) {
try {
expiresIn = Long.parseLong(expiresInParam);
} catch (NumberFormatException ignored) {
// If the expires_in parameter is not a valid number, ignore it and treat as unspecified.
expiresIn = null;
}
}
return new Tokens(request.getParameter(KEY_ACCESS_TOKEN), request.getParameter(KEY_ID_TOKEN), null,
request.getParameter(KEY_TOKEN_TYPE), expiresIn, originDomain, originIssuer);
}
Copilot is powered by AI and may make mistakes. Always verify output.
@tanya732 tanya732 marked this pull request as ready for review March 18, 2026 08:37
@tanya732 tanya732 requested a review from a team as a code owner March 18, 2026 08:37
@tanya732 tanya732 merged commit b6d2c8f into master Apr 9, 2026
4 checks passed
@tanya732 tanya732 deleted the feat/mcd-support branch April 9, 2026 08:46
This was referenced Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants