From 9b6fe0b070893a925788e1ec2993e95d2eaa48f6 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 29 May 2026 01:37:57 +0530 Subject: [PATCH] Add Migration Guide --- MIGRATION_GUIDE.md | 456 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 MIGRATION_GUIDE.md diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 0000000..9e4c34d --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,456 @@ +# Migrating from v1.x to v2.0.0 + +This guide covers the changes required to migrate your application from `mvc-auth-commons` v1.x to v2.0.0. + +## Overview of Changes + +v2.0.0 is a major release that includes: + +- **Platform upgrade:** Java 17 minimum, Jakarta Servlet 6.0 (replaces `javax.servlet`) +- **Security hardening:** HMAC-signed origin domain cookies bound to state, ID Token signature always verified, algorithm auto-detection +- **Deprecated API removal:** Session-based methods and classes removed +- **ID Token validation rewrite:** Delegated to `auth0-java` v3 (removes custom verification stack) +- **Multi-tab login fix:** Transaction-keyed cookies prevent concurrent login race conditions +- **JPMS module support:** Declared as `com.auth0.mvc.commons` module +- **Dependency upgrades:** auth0-java v3.5.1, java-jwt v4.5.0, jwks-rsa v0.24.1, Gradle 8.x + +--- + +## Requirements + +| | v1.x | v2.0.0 | +|---|---|---| +| **Java** | 8+ | 17+ | +| **Servlet API** | `javax.servlet` 3.1+ | `jakarta.servlet` 6.0+ | +| **Servlet Container** | Tomcat 8.5+, Jetty 9+, WildFly 14+ | Tomcat 10.1+, Jetty 12+, WildFly 27+ | +| **auth0-java** | 1.x / 2.x | 3.x (3.5.1+) | +| **java-jwt** | 3.x | 4.x (4.5.0+) | +| **jwks-rsa** | 0.21.x | 0.24.x | +| **Spring Boot** (if applicable) | 2.x | 3.x | +| **Gradle** (if building from source) | 6.x | 8.x | + +--- + +## Installation + +Update your dependency version: + +**Maven:** +```xml + + com.auth0 + mvc-auth-commons + 2.0.0-beta.0 + +``` + +**Gradle:** +```groovy +implementation 'com.auth0:mvc-auth-commons:2.0.0-beta.0' +``` + +--- + +## Breaking Changes + +### 1. Namespace: `javax.servlet` to `jakarta.servlet` + +All servlet imports must be updated: + +```diff +- import javax.servlet.http.HttpServletRequest; +- import javax.servlet.http.HttpServletResponse; +- import javax.servlet.http.HttpSession; +- import javax.servlet.http.Cookie; ++ import jakarta.servlet.http.HttpServletRequest; ++ import jakarta.servlet.http.HttpServletResponse; ++ import jakarta.servlet.http.HttpSession; ++ import jakarta.servlet.http.Cookie; +``` + +This applies to all classes that reference `HttpServletRequest`, `HttpServletResponse`, `HttpSession`, `Cookie`, `Filter`, `Servlet`, etc. + +> **Note:** If you are using Spring Boot, upgrading from Spring Boot 2.x to 3.x handles this namespace change for your application code. However, your dependency on `mvc-auth-commons` must also be upgraded to v2. + +--- + +### 2. Removed: `handle(HttpServletRequest)` + +The single-parameter `handle()` method that used the HTTP session for state management has been removed. + +**Before (v1):** +```java +Tokens tokens = authController.handle(request); +``` + +**After (v2):** +```java +Tokens tokens = authController.handle(request, response); +``` + +The two-parameter version uses secure, transient cookies (instead of server-side sessions) for state and nonce storage. This is required for compatibility with SameSite cookie restrictions in modern browsers. + +--- + +### 3. Removed: `buildAuthorizeUrl(HttpServletRequest, String)` + +The two-parameter `buildAuthorizeUrl()` that used the HTTP session has been removed. + +**Before (v1):** +```java +String authorizeUrl = authController.buildAuthorizeUrl(request, redirectUri).build(); +``` + +**After (v2):** +```java +String authorizeUrl = authController.buildAuthorizeUrl(request, response, redirectUri).build(); +``` + +The `response` parameter is required so that state and nonce cookies can be set on the response. + +--- + +### 4. Removed: `InvalidRequestException.getDescription()` + +The deprecated `getDescription()` method has been removed. Use `getMessage()` instead. + +**Before (v1):** +```java +catch (InvalidRequestException e) { + String desc = e.getDescription(); +} +``` + +**After (v2):** +```java +catch (InvalidRequestException e) { + String desc = e.getMessage(); +} +``` + +--- + +### 5. Removed: `withHttpOptions(HttpOptions)` on Builder + +The `HttpOptions` configuration on `AuthenticationController.Builder` has been removed. The underlying HTTP client is now managed by `auth0-java` v3's `DefaultHttpClient`. + +**Before (v1):** +```java +HttpOptions options = new HttpOptions(); +options.setConnectTimeout(10); +options.setReadTimeout(10); + +AuthenticationController controller = AuthenticationController + .newBuilder(domain, clientId, clientSecret) + .withHttpOptions(options) + .build(); +``` + +**After (v2):** +```java +// HTTP client configuration is managed internally by auth0-java v3. +// If you need custom HTTP settings, configure them via AuthAPI directly. +AuthenticationController controller = AuthenticationController + .newBuilder(domain, clientId, clientSecret) + .build(); +``` + +--- + +### 6. Removed: Custom Signature Verifier Classes + +The following classes have been removed. ID Token verification is now handled internally by `auth0-java` v3's `IdTokenVerifier`: + +| Removed Class | v2 Replacement | +|---|---| +| `com.auth0.IdTokenVerifier` | `com.auth0.utils.tokens.IdTokenVerifier` (internal, from auth0-java v3) | +| `com.auth0.SignatureVerifier` | `com.auth0.utils.tokens.SignatureVerifier` (internal, from auth0-java v3) | +| `com.auth0.AsymmetricSignatureVerifier` | Per-domain JwkProvider resolution (internal) | +| `com.auth0.SymmetricSignatureVerifier` | `SignatureVerifier.forHS256(clientSecret)` (internal) | +| `com.auth0.AlgorithmNameVerifier` | Removed entirely — signature is always verified | +| `com.auth0.TokenValidationException` | `com.auth0.exception.IdTokenValidationException` (from auth0-java v3) | + +If your code references any of these classes directly, remove those references. The library now handles all token verification internally. + +--- + +### 7. Removed: Session-Based Storage Classes + +| Removed Class | Purpose | v2 Replacement | +|---|---|---| +| `RandomStorage` | Session-based state/nonce storage | `TransientCookieStore` (cookie-based) | +| `SessionUtils` | HTTP session utilities | Removed — cookies only | + +If your code references `RandomStorage` or `SessionUtils`, remove those references. The library exclusively uses transient cookies for state management. + +--- + +### 8. Algorithm Auto-Detection (Behavior Change) + +In v1, you had to configure the signing algorithm explicitly: +- HS256 was the default for implicit flows +- RS256 required configuring a `JwkProvider` + +In v2, the algorithm is read automatically from the token's `alg` header: + +- **RS256 tokens:** Verified using the configured `JwkProvider`, or one auto-discovered from the domain's `/.well-known/jwks.json` endpoint +- **HS256 tokens:** Verified using the client secret + +You should still configure a `JwkProvider` if you want to control caching, rate-limiting, or connection settings: + +```java +JwkProvider jwkProvider = new JwkProviderBuilder("your-tenant.auth0.com") + .cached(10, 24, TimeUnit.HOURS) + .rateLimited(10, 1, TimeUnit.MINUTES) + .build(); + +AuthenticationController controller = AuthenticationController + .newBuilder(domain, clientId, clientSecret) + .withJwkProvider(jwkProvider) + .build(); +``` + +--- + +### 9. auth0-java v3 API Changes + +If your application imports `auth0-java` classes directly, note these changes: + +| v1 (auth0-java 1.x/2.x) | v2 (auth0-java 3.x) | +|---|---| +| `new AuthAPI(domain, clientId, clientSecret)` | `AuthAPI.newBuilder(domain, clientId, clientSecret).build()` | +| `AuthAPI.authorizeUrl(redirectUri)` | Same (unchanged) | +| `AuthAPI.exchangeCode(code, redirectUri)` | Same (unchanged) | +| `TokenHolder.getIdToken()` | Same (unchanged) | + +--- + +## What's New in v2 + +### MCD Security Hardening + +Multiple Custom Domains (MCD) support was introduced in v1.12.0. In v2, MCD has been hardened with: + +- **HMAC-signed origin domain cookie:** The origin domain is cryptographically bound to the `state` parameter using HMAC-SHA256 with the client secret. This prevents cookie replay or tampering across different authentication transactions. +- **Per-domain JwkProvider cache:** The library maintains an internal `ConcurrentHashMap` cache. JWKS endpoints are only contacted once per domain (per JVM lifetime). If a customer-provided `JwkProvider` is configured via `withJwkProvider()`, it takes precedence for all domains. +- **ID Token signature always verified per issuer:** In v1, the `AlgorithmNameVerifier` could skip signature verification in certain MCD code flow paths. In v2, the ID Token signature is always verified against the correct issuer's JWKS. + +These improvements are transparent to application code — no changes required if you're already using MCD in v1. + +--- + +### Transaction-Keyed Cookies (Multi-Tab Login Fix) + +v1 used a single fixed cookie name (`com.auth0.state`) shared across all browser tabs. Concurrent logins would overwrite each other, causing state validation failures. + +v2 embeds the state value in the cookie name, isolating each login flow: + +``` +# v1 — race condition: last tab wins +com.auth0.state = +com.auth0.nonce = + +# v2 — each tab gets its own cookie +com.auth0.state.abc123 = abc123 +com.auth0.nonce.abc123 = +com.auth0.state.xyz789 = xyz789 +com.auth0.nonce.xyz789 = +``` + +**Backward compatible during rolling upgrades:** On callback, v2 checks for the transaction-keyed cookie first, then falls back to the legacy fixed-name cookie for in-flight transactions that started before the upgrade. + +--- + +### ID Token Signature Always Verified + +In v1, the `AlgorithmNameVerifier` could skip signature verification in certain code flow paths. In v2, the ID Token signature is **always** verified — either via RS256 (JwkProvider) or HS256 (client secret). There is no code path that allows unverified tokens. + +--- + +### Java Module System (JPMS) Support + +v2 includes a `module-info.java` descriptor. The module name is `com.auth0.mvc.commons`: + +```java +module com.auth0.mvc.commons { + exports com.auth0; + + requires transitive com.auth0.java; + requires transitive com.auth0.jwt; + requires transitive com.auth0.jwks; + requires transitive jakarta.servlet; + requires org.apache.commons.lang3; + requires org.apache.commons.codec; + requires com.google.common; +} +``` + +If your application uses JPMS, add `requires com.auth0.mvc.commons;` to your `module-info.java`. + +--- + +## Removed APIs — Complete Reference + +### Deleted Classes + +| Class | Purpose | v2 Replacement | +|---|---|---| +| `IdTokenVerifier` | Custom ID token validation | auth0-java v3's `IdTokenVerifier` (internal) | +| `SignatureVerifier` | Base class for token signature verification | Auto-detection from `alg` header (internal) | +| `AsymmetricSignatureVerifier` | RS256 signature verification | Per-domain `JwkProvider` resolution (internal) | +| `SymmetricSignatureVerifier` | HS256 signature verification | `SignatureVerifier.forHS256()` (internal) | +| `AlgorithmNameVerifier` | Algorithm allowlist check | Removed — always verifies signature | +| `TokenValidationException` | Custom validation exception | `com.auth0.exception.IdTokenValidationException` | +| `RandomStorage` | Session-based state/nonce storage | `TransientCookieStore` (cookie-based) | +| `SessionUtils` | HTTP session utilities | Removed — cookies only | + +### Deleted Methods + +| Class | Method | Replacement | +|---|---|---| +| `AuthenticationController` | `handle(HttpServletRequest)` | `handle(HttpServletRequest, HttpServletResponse)` | +| `AuthenticationController` | `buildAuthorizeUrl(HttpServletRequest, String)` | `buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)` | +| `AuthenticationController.Builder` | `withHttpOptions(HttpOptions)` | Removed (auth0-java v3 manages HTTP client) | +| `InvalidRequestException` | `getDescription()` | `getMessage()` | + +--- + +## Migration Checklist + +Use this checklist to verify your migration is complete: + +- [ ] **Runtime:** Upgrade Java to 17+ +- [ ] **Container:** Upgrade servlet container to Jakarta EE 10 compatible (Tomcat 10.1+, Jetty 12+, WildFly 27+) +- [ ] **Imports:** Update all `javax.servlet.*` imports to `jakarta.servlet.*` +- [ ] **handle():** Replace `handle(request)` with `handle(request, response)` +- [ ] **buildAuthorizeUrl():** Replace `buildAuthorizeUrl(request, uri)` with `buildAuthorizeUrl(request, response, uri)` +- [ ] **getDescription():** Replace `InvalidRequestException.getDescription()` with `getMessage()` +- [ ] **HttpOptions:** Remove any `withHttpOptions()` calls from Builder +- [ ] **Deleted classes:** Remove references to `SignatureVerifier`, `AsymmetricSignatureVerifier`, `SymmetricSignatureVerifier`, `IdTokenVerifier`, `AlgorithmNameVerifier`, `TokenValidationException`, `RandomStorage`, `SessionUtils` +- [ ] **auth0-java:** Update `auth0-java` dependency to v3.x if used directly in your app +- [ ] **Spring Boot:** If applicable, upgrade to Spring Boot 3.x +- [ ] **JPMS:** If using Java modules, add `requires com.auth0.mvc.commons;` +- [ ] **Test:** Verify login flow works end-to-end (authorize -> callback -> tokens) +- [ ] **Test:** Verify multi-tab login works (open login in two tabs simultaneously) +- [ ] **Test:** If using MCD, verify each custom domain resolves and validates correctly + +--- + +## Full Example (v2) + +### Configuration + +```java +import com.auth0.AuthenticationController; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.JwkProviderBuilder; + +import java.util.concurrent.TimeUnit; + +public class Auth0Config { + private static final String DOMAIN = "your-tenant.auth0.com"; + private static final String CLIENT_ID = "YOUR_CLIENT_ID"; + private static final String CLIENT_SECRET = "YOUR_CLIENT_SECRET"; + + private static final AuthenticationController controller; + + static { + JwkProvider jwkProvider = new JwkProviderBuilder(DOMAIN) + .cached(10, 24, TimeUnit.HOURS) + .rateLimited(10, 1, TimeUnit.MINUTES) + .build(); + + controller = AuthenticationController + .newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET) + .withJwkProvider(jwkProvider) + .build(); + } + + public static AuthenticationController getController() { + return controller; + } +} +``` + +### Login Servlet + +```java +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet(urlPatterns = {"/login"}) +public class LoginServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { + String authorizeUrl = Auth0Config.getController() + .buildAuthorizeUrl(req, res, "http://localhost:3000/callback") + .withScope("openid profile email") + .build(); + res.sendRedirect(authorizeUrl); + } +} +``` + +### Callback Servlet + +```java +import com.auth0.IdentityVerificationException; +import com.auth0.Tokens; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet(urlPatterns = {"/callback"}) +public class CallbackServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { + try { + Tokens tokens = Auth0Config.getController().handle(req, res); + req.getSession().setAttribute("id_token", tokens.getIdToken()); + req.getSession().setAttribute("access_token", tokens.getAccessToken()); + res.sendRedirect("/dashboard"); + } catch (IdentityVerificationException e) { + res.sendRedirect("/login?error=" + e.getCode()); + } + } +} +``` + +--- + +## Troubleshooting + +### "The received state doesn't match the expected one" + +This error occurs when the state cookie is not found on callback. Common causes: + +1. **Cookie blocked by SameSite policy:** Ensure your callback is served over HTTPS in production. Use `.withSecureCookie(true)` if needed. +2. **Cookie path mismatch:** If your app is deployed at a sub-path, configure `.withCookiePath("/your-app")`. +3. **Third-party cookie restrictions:** Some browsers block cookies in cross-origin iframes. Avoid embedding the login flow in an iframe. + +### "Failed to get public key for key ID" + +This error occurs when the JwkProvider cannot fetch the signing key. Common causes: + +1. **Network connectivity:** Ensure the server can reach `https://your-tenant.auth0.com/.well-known/jwks.json`. +2. **Rate limiting:** If using the default auto-discovered JwkProvider, it has no rate limiting configured. For high-traffic apps, configure a `JwkProvider` with caching. + +### ClassNotFoundException for `javax.servlet.*` + +Your application or a dependency still references the old `javax.servlet` namespace. Check: +1. All your code uses `jakarta.servlet.*` imports +2. Your servlet container is Jakarta EE 10 compatible +3. No transitive dependencies pull in the old `javax.servlet-api` + +--- + +## Support + +- [API Reference (JavaDocs)](https://javadoc.io/doc/com.auth0/mvc-auth-commons) +- [Examples](./EXAMPLES.md) +- [Report an issue](https://github.com/auth0/auth0-java-mvc-common/issues)