From d48997e8bba3060a6ba64ab6b03c694647152be4 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 6 May 2026 15:15:13 +0530 Subject: [PATCH] Feat: Modified Id Token Validation --- .../java/com/auth0/AlgorithmNameVerifier.java | 10 - .../auth0/AsymmetricSignatureVerifier.java | 50 -- .../com/auth0/AuthenticationController.java | 41 +- src/main/java/com/auth0/IdTokenVerifier.java | 202 ------ src/main/java/com/auth0/RequestProcessor.java | 117 ++- .../java/com/auth0/SignatureVerifier.java | 59 -- .../com/auth0/SymmetricSignatureVerifier.java | 20 - .../com/auth0/TokenValidationException.java | 12 - .../java/com/auth0/IdTokenVerifierTest.java | 680 ------------------ .../java/com/auth0/RequestProcessorTest.java | 61 +- .../java/com/auth0/SignatureVerifierTest.java | 185 ----- 11 files changed, 112 insertions(+), 1325 deletions(-) delete mode 100644 src/main/java/com/auth0/AlgorithmNameVerifier.java delete mode 100644 src/main/java/com/auth0/AsymmetricSignatureVerifier.java delete mode 100644 src/main/java/com/auth0/IdTokenVerifier.java delete mode 100644 src/main/java/com/auth0/SignatureVerifier.java delete mode 100644 src/main/java/com/auth0/SymmetricSignatureVerifier.java delete mode 100644 src/main/java/com/auth0/TokenValidationException.java delete mode 100644 src/test/java/com/auth0/IdTokenVerifierTest.java delete mode 100644 src/test/java/com/auth0/SignatureVerifierTest.java diff --git a/src/main/java/com/auth0/AlgorithmNameVerifier.java b/src/main/java/com/auth0/AlgorithmNameVerifier.java deleted file mode 100644 index 2779b80..0000000 --- a/src/main/java/com/auth0/AlgorithmNameVerifier.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.auth0; - -@SuppressWarnings("unused") -class AlgorithmNameVerifier extends SignatureVerifier { - - AlgorithmNameVerifier() { - //Must only allow supported algorithms and never "none" algorithm - super(null, "HS256", "RS256"); - } -} diff --git a/src/main/java/com/auth0/AsymmetricSignatureVerifier.java b/src/main/java/com/auth0/AsymmetricSignatureVerifier.java deleted file mode 100644 index b8b82b4..0000000 --- a/src/main/java/com/auth0/AsymmetricSignatureVerifier.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.auth0; - -import com.auth0.jwk.Jwk; -import com.auth0.jwk.JwkException; -import com.auth0.jwk.JwkProvider; -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.interfaces.RSAKeyProvider; - -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; - -@SuppressWarnings("unused") -class AsymmetricSignatureVerifier extends SignatureVerifier { - - AsymmetricSignatureVerifier(JwkProvider jwkProvider) { - super(createJWTVerifier(jwkProvider), "RS256"); - } - - private static JWTVerifier createJWTVerifier(final JwkProvider jwkProvider) { - Algorithm alg = Algorithm.RSA256(new RSAKeyProvider() { - @Override - public RSAPublicKey getPublicKeyById(String keyId) { - try { - Jwk jwk = jwkProvider.get(keyId); - return (RSAPublicKey) jwk.getPublicKey(); - } catch (JwkException ignored) { - // JwkException handled by Algorithm verify implementation from java-jwt - } - return null; - } - - @Override - public RSAPrivateKey getPrivateKey() { - //NO-OP - return null; - } - - @Override - public String getPrivateKeyId() { - //NO-OP - return null; - } - }); - return JWT.require(alg) - .ignoreIssuedAt() - .build(); - } -} diff --git a/src/main/java/com/auth0/AuthenticationController.java b/src/main/java/com/auth0/AuthenticationController.java index 8a14c9a..c768765 100644 --- a/src/main/java/com/auth0/AuthenticationController.java +++ b/src/main/java/com/auth0/AuthenticationController.java @@ -1,6 +1,5 @@ package com.auth0; -import com.auth0.client.auth.AuthAPI; import com.auth0.jwk.JwkProvider; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.Validate; @@ -256,19 +255,20 @@ public AuthenticationController build() throws UnsupportedOperationException { ? new StaticDomainProvider(domain) : new ResolverDomainProvider(domainResolver); - SignatureVerifier signatureVerifier = buildSignatureVerifier(); - - RequestProcessor processor = new RequestProcessor.Builder(domainProvider, responseType, clientId, - clientSecret, signatureVerifier) + RequestProcessor.Builder builder = new RequestProcessor.Builder( + domainProvider, responseType, clientId, clientSecret) .withClockSkew(clockSkew) .withAuthenticationMaxAge(authenticationMaxAge) .withLegacySameSiteCookie(useLegacySameSiteCookie) .withOrganization(organization) .withInvitation(invitation) - .withCookiePath(cookiePath) - .build(); + .withCookiePath(cookiePath); + + if (jwkProvider != null) { + builder.withJwkProvider(jwkProvider); + } - return new AuthenticationController(processor); + return new AuthenticationController(builder.build()); } private void validateDomainConfiguration() { @@ -279,31 +279,6 @@ private void validateDomainConfiguration() { throw new IllegalStateException("Cannot specify both domain and domainResolver."); } } - - private SignatureVerifier buildSignatureVerifier() { - if (jwkProvider != null) { - return new AsymmetricSignatureVerifier(jwkProvider); - } - if (responseType.contains(RESPONSE_TYPE_CODE)) { - return new AlgorithmNameVerifier(); // legacy behavior - } - return new SymmetricSignatureVerifier(clientSecret); - } - - @VisibleForTesting - IdTokenVerifier.Options createIdTokenVerificationOptions(String issuer, String audience, SignatureVerifier signatureVerifier) { - return new IdTokenVerifier.Options(issuer, audience, signatureVerifier); - } - - private String getIssuer(String domain) { - if (!domain.startsWith("http://") && !domain.startsWith("https://")) { - domain = "https://" + domain; - } - if (!domain.endsWith("/")) { - domain = domain + "/"; - } - return domain; - } } /** diff --git a/src/main/java/com/auth0/IdTokenVerifier.java b/src/main/java/com/auth0/IdTokenVerifier.java deleted file mode 100644 index 3ef0e32..0000000 --- a/src/main/java/com/auth0/IdTokenVerifier.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.auth0; - -import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.lang3.Validate; - -import java.util.Calendar; -import java.util.Date; -import java.util.List; - -/** - * Token verification utility class. - * Supported signing algorithms: HS256 and RS256 - */ -class IdTokenVerifier { - - private static final Integer DEFAULT_CLOCK_SKEW = 60; //1 min = 60 sec - - private static final String NONCE_CLAIM = "nonce"; - private static final String AZP_CLAIM = "azp"; - private static final String AUTH_TIME_CLAIM = "auth_time"; - - /** - * Verifies a provided ID Token follows the OIDC specification. - * @see Open ID Connect Specification - * - * @param token the ID Token to verify. - * @param verifyOptions the verification options, like audience, issuer, algorithm. - * @throws TokenValidationException If the ID Token is null, its signing algorithm not supported, its signature invalid or one of its claim invalid. - */ - void verify(String token, Options verifyOptions) throws TokenValidationException { - Validate.notNull(verifyOptions); - - if (isEmpty(token)) { - throw new TokenValidationException("ID token is required but missing"); - } - - DecodedJWT decoded = verifyOptions.verifier.verifySignature(token); - - if (isEmpty(decoded.getIssuer())) { - throw new TokenValidationException("Issuer (iss) claim must be a string present in the ID token"); - } - if (!decoded.getIssuer().equals(verifyOptions.issuer)) { - throw new TokenValidationException(String.format("Issuer (iss) claim mismatch in the ID token, expected \"%s\", found \"%s\"", verifyOptions.issuer, decoded.getIssuer())); - } - - if (isEmpty(decoded.getSubject())) { - throw new TokenValidationException("Subject (sub) claim must be a string present in the ID token"); - } - - final List audience = decoded.getAudience(); - if (audience == null) { - throw new TokenValidationException("Audience (aud) claim must be a string or array of strings present in the ID token"); - } - if (!audience.contains(verifyOptions.audience)) { - throw new TokenValidationException(String.format("Audience (aud) claim mismatch in the ID token; expected \"%s\" but found \"%s\"", verifyOptions.audience, decoded.getAudience())); - } - - // validate org if set - if (verifyOptions.organization != null) { - String org = verifyOptions.organization.trim(); - if (org.startsWith("org_")) { - // org ID - String orgIdClaim = decoded.getClaim("org_id").asString(); - if (isEmpty(orgIdClaim)) { - throw new TokenValidationException("Organization Id (org_id) claim must be a string present in the ID token"); - } - if (!org.equals(orgIdClaim)) { - throw new TokenValidationException(String.format("Organization (org_id) claim mismatch in the ID token; expected \"%s\" but found \"%s\"", verifyOptions.organization, orgIdClaim)); - } - } - else { - // org name - String orgNameClaim = decoded.getClaim("org_name").asString(); - if (isEmpty(orgNameClaim)) { - throw new TokenValidationException("Organization name (org_name) claim must be a string present in the ID token"); - } - if (!org.toLowerCase().equals(orgNameClaim)) { - throw new TokenValidationException(String.format("Organization (org_name) claim mismatch in the ID token; expected \"%s\" but found \"%s\"", verifyOptions.organization, orgNameClaim)); - } - } - } - - // TODO refactor to modern date/time APIs - final Calendar cal = Calendar.getInstance(); - final Date now = verifyOptions.clock != null ? verifyOptions.clock : cal.getTime(); - final int clockSkew = verifyOptions.clockSkew != null ? verifyOptions.clockSkew : DEFAULT_CLOCK_SKEW; - - if (decoded.getExpiresAt() == null) { - throw new TokenValidationException("Expiration Time (exp) claim must be a number present in the ID token"); - } - - cal.setTime(decoded.getExpiresAt()); - cal.add(Calendar.SECOND, clockSkew); - Date expDate = cal.getTime(); - - if (now.after(expDate)) { - throw new TokenValidationException(String.format("Expiration Time (exp) claim error in the ID token; current time (%d) is after expiration time (%d)", now.getTime() / 1000, expDate.getTime() / 1000)); - } - - if (decoded.getIssuedAt() == null) { - throw new TokenValidationException("Issued At (iat) claim must be a number present in the ID token"); - } - - cal.setTime(decoded.getIssuedAt()); - cal.add(Calendar.SECOND, -1 * clockSkew); - - if (verifyOptions.nonce != null) { - String nonceClaim = decoded.getClaim(NONCE_CLAIM).asString(); - if (isEmpty(nonceClaim)) { - throw new TokenValidationException("Nonce (nonce) claim must be a string present in the ID token"); - } - if (!verifyOptions.nonce.equals(nonceClaim)) { - throw new TokenValidationException(String.format("Nonce (nonce) claim mismatch in the ID token; expected \"%s\", found \"%s\"", verifyOptions.nonce, nonceClaim)); - } - } - - if (audience.size() > 1) { - String azpClaim = decoded.getClaim(AZP_CLAIM).asString(); - if (isEmpty(azpClaim)) { - throw new TokenValidationException("Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values"); - } - if (!verifyOptions.audience.equals(azpClaim)) { - throw new TokenValidationException(String.format("Authorized Party (azp) claim mismatch in the ID token; expected \"%s\", found \"%s\"", verifyOptions.audience, azpClaim)); - } - } - - if (verifyOptions.maxAge != null) { - Date authTime = decoded.getClaim(AUTH_TIME_CLAIM).asDate(); - if (authTime == null) { - throw new TokenValidationException("Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified"); - } - - cal.setTime(authTime); - cal.add(Calendar.SECOND, verifyOptions.maxAge); - cal.add(Calendar.SECOND, clockSkew); - Date authTimeDate = cal.getTime(); - - if (now.after(authTimeDate)) { - throw new TokenValidationException(String.format("Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (%d) is after last auth at (%d)", now.getTime() / 1000, authTimeDate.getTime() / 1000)); - } - } - } - - private boolean isEmpty(String value) { - return value == null || value.isEmpty(); - } - - static class Options { - String issuer; - final String audience; - final SignatureVerifier verifier; - String nonce; - private Integer maxAge; - Integer clockSkew; - Date clock; - String organization; - - public Options(String issuer, String audience, SignatureVerifier verifier) { - Validate.notNull(issuer, "Issuer must not be null"); - Validate.notNull(audience, "Audience must not be null"); - Validate.notNull(verifier, "SignatureVerifier must not be null"); - this.issuer = issuer; - this.audience = audience; - this.verifier = verifier; - } - - public Options(String audience, SignatureVerifier verifier) { - Validate.notNull(audience, "Audience must not be null"); - Validate.notNull(verifier, "SignatureVerifier must not be null"); - this.audience = audience; - this.verifier = verifier; - } - - void setIssuer(String issuer) { - this.issuer = issuer; - } - - void setNonce(String nonce) { - this.nonce = nonce; - } - - void setMaxAge(Integer maxAge) { - this.maxAge = maxAge; - } - - void setClockSkew(Integer clockSkew) { - this.clockSkew = clockSkew; - } - - void setClock(Date now) { - this.clock = now; - } - - Integer getMaxAge() { - return maxAge; - } - - void setOrganization(String organization) { - this.organization = organization; - } - } -} diff --git a/src/main/java/com/auth0/RequestProcessor.java b/src/main/java/com/auth0/RequestProcessor.java index 0919da6..3b86d77 100644 --- a/src/main/java/com/auth0/RequestProcessor.java +++ b/src/main/java/com/auth0/RequestProcessor.java @@ -3,14 +3,26 @@ import com.auth0.client.LoggingOptions; import com.auth0.client.auth.AuthAPI; import com.auth0.exception.Auth0Exception; +import com.auth0.exception.IdTokenValidationException; +import com.auth0.exception.PublicKeyProviderException; +import com.auth0.jwt.JWT; import com.auth0.json.auth.TokenHolder; +import com.auth0.jwk.Jwk; +import com.auth0.jwk.JwkException; +import com.auth0.jwk.JwkProvider; +import com.auth0.jwk.UrlJwkProvider; import com.auth0.net.client.DefaultHttpClient; +import com.auth0.utils.tokens.IdTokenVerifier; +import com.auth0.utils.tokens.SignatureVerifier; import org.apache.commons.lang3.Validate; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.security.interfaces.RSAPublicKey; import java.util.Arrays; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import static com.auth0.InvalidRequestException.*; @@ -33,34 +45,32 @@ class RequestProcessor { private static final String KEY_FORM_POST = "form_post"; private static final String KEY_MAX_AGE = "max_age"; - // Visible for testing - private final DomainProvider domainProvider; private final String responseType; private final String clientId; private final String clientSecret; - private SignatureVerifier signatureVerifier; + private final JwkProvider jwkProvider; - // Configuration values passed from Builder for creating per-request - // verification options private final Integer clockSkew; private final Integer authenticationMaxAge; private final String organization; private final String invitation; final boolean useLegacySameSiteCookie; - private final IdTokenVerifier tokenVerifier; private final String cookiePath; private boolean loggingEnabled = false; private boolean telemetryDisabled = false; + // Cache JwkProviders per domain for MCD support + private final ConcurrentMap jwkProviders = new ConcurrentHashMap<>(); + static class Builder { private final DomainProvider domainProvider; private final String responseType; private final String clientId; private final String clientSecret; - private final SignatureVerifier signatureVerifier; + private JwkProvider jwkProvider; private boolean useLegacySameSiteCookie = true; private Integer clockSkew; private Integer authenticationMaxAge; @@ -71,13 +81,16 @@ static class Builder { public Builder(DomainProvider domainProvider, String responseType, String clientId, - String clientSecret, - SignatureVerifier signatureVerifier) { + String clientSecret) { this.domainProvider = domainProvider; this.responseType = responseType; this.clientId = clientId; this.clientSecret = clientSecret; - this.signatureVerifier = signatureVerifier; + } + + Builder withJwkProvider(JwkProvider jwkProvider) { + this.jwkProvider = jwkProvider; + return this; } public Builder withClockSkew(Integer clockSkew) { @@ -111,25 +124,22 @@ Builder withInvitation(String invitation) { } RequestProcessor build() { - return new RequestProcessor(domainProvider, responseType, clientId, clientSecret, - signatureVerifier, new IdTokenVerifier(), - useLegacySameSiteCookie, clockSkew, authenticationMaxAge, organization, invitation, cookiePath); + jwkProvider, useLegacySameSiteCookie, clockSkew, authenticationMaxAge, + organization, invitation, cookiePath); } } - private RequestProcessor(DomainProvider domainProvider, String responseType, String clientId, String clientSecret, SignatureVerifier signatureVerifier, IdTokenVerifier tokenVerifier, + private RequestProcessor(DomainProvider domainProvider, String responseType, String clientId, + String clientSecret, JwkProvider jwkProvider, boolean useLegacySameSiteCookie, Integer clockSkew, Integer authenticationMaxAge, String organization, String invitation, String cookiePath) { this.domainProvider = domainProvider; this.responseType = responseType; this.clientId = clientId; this.clientSecret = clientSecret; - this.signatureVerifier = signatureVerifier; - this.tokenVerifier = tokenVerifier; + this.jwkProvider = jwkProvider; this.useLegacySameSiteCookie = useLegacySameSiteCookie; - - // Store individual configuration values instead of pre-built verifyOptions this.clockSkew = clockSkew; this.authenticationMaxAge = authenticationMaxAge; this.organization = organization; @@ -245,7 +255,7 @@ static boolean requiresFormPostResponseMode(List responseType) { * Obtains code request tokens (if using Code flow) and validates the ID token. * @param request the HTTP request * @param frontChannelTokens the tokens obtained from the front channel - * @param responseTypeList the reponse types + * @param responseTypeList the response types * @return a Tokens object that wraps the values obtained from the front-channel and/or the code request response. * @throws IdentityVerificationException */ @@ -257,14 +267,10 @@ private Tokens getVerifiedTokens(HttpServletRequest request, HttpServletResponse String nonce = TransientCookieStore.getNonce(request, response); - IdTokenVerifier.Options requestVerifyOptions = createRequestVerifyOptions(originIssuer, nonce); - try { if (responseTypeList.contains(KEY_ID_TOKEN)) { // Implicit/Hybrid flow: must verify front-channel ID Token first. - // The issuer is derived from the HMAC-verified domain, so this check - // validates the token's iss against a trusted value. - tokenVerifier.verify(frontChannelTokens.getIdToken(), requestVerifyOptions); + verifyIdToken(frontChannelTokens.getIdToken(), originIssuer, originDomain, nonce); } if (responseTypeList.contains(KEY_CODE)) { // Code/Hybrid flow @@ -274,11 +280,11 @@ private Tokens getVerifiedTokens(HttpServletRequest request, HttpServletResponse // If we already verified the front-channel token, don't verify it again. String idTokenFromCodeExchange = codeExchangeTokens.getIdToken(); if (idTokenFromCodeExchange != null) { - tokenVerifier.verify(idTokenFromCodeExchange, requestVerifyOptions); + verifyIdToken(idTokenFromCodeExchange, originIssuer, originDomain, nonce); } } } - } catch (TokenValidationException e) { + } catch (IdTokenValidationException e) { throw new IdentityVerificationException(JWT_VERIFICATION_ERROR, "An error occurred while trying to verify the ID Token.", e); } catch (Auth0Exception e) { throw new IdentityVerificationException(API_ERROR, "An error occurred while exchanging the authorization code.", e); @@ -288,27 +294,60 @@ private Tokens getVerifiedTokens(HttpServletRequest request, HttpServletResponse } /** - * Creates per-request verification options to avoid thread safety issues. - * This creates fresh options from the stored configuration values. + * Verifies an ID token using auth0-java v3's IdTokenVerifier. + * The signature verification strategy is determined by the token's alg header: + * - RS256: uses JwkProvider (customer-provided or auto-discovered per domain) + * - HS256: uses client secret */ - private IdTokenVerifier.Options createRequestVerifyOptions(String issuer, String nonce) { - // Create fresh verification options for this specific request - IdTokenVerifier.Options requestOptions = new IdTokenVerifier.Options(clientId, signatureVerifier); + private void verifyIdToken(String idToken, String issuer, String domain, String nonce) throws IdTokenValidationException { + SignatureVerifier sigVerifier = buildSignatureVerifier(idToken, domain); - requestOptions.setIssuer(issuer); - requestOptions.setNonce(nonce); + IdTokenVerifier.Builder verifierBuilder = IdTokenVerifier.init(issuer, clientId, sigVerifier); if (clockSkew != null) { - requestOptions.setClockSkew(clockSkew); - } - if (authenticationMaxAge != null) { - requestOptions.setMaxAge(authenticationMaxAge); + verifierBuilder.withLeeway(clockSkew); } if (organization != null) { - requestOptions.setOrganization(organization); + verifierBuilder.withOrganization(organization); } - return requestOptions; + IdTokenVerifier verifier = verifierBuilder.build(); + verifier.verify(idToken, nonce, authenticationMaxAge); + } + + /** + * Builds the appropriate SignatureVerifier based on the token's algorithm header. + * - If alg is HS256: use client secret + * - If alg is RS256: use JwkProvider (customer-provided or auto-discovered from domain) + */ + private SignatureVerifier buildSignatureVerifier(String idToken, String domain) { + String algorithm = JWT.decode(idToken).getAlgorithm(); + + if ("HS256".equals(algorithm)) { + return SignatureVerifier.forHS256(clientSecret); + } + + // RS256 (default): use JwkProvider + JwkProvider provider = getJwkProvider(domain); + return SignatureVerifier.forRS256(keyId -> { + try { + Jwk jwk = provider.get(keyId); + return (RSAPublicKey) jwk.getPublicKey(); + } catch (JwkException e) { + throw new PublicKeyProviderException("Failed to get public key for key ID: " + keyId, e); + } + }); + } + + /** + * Gets the JwkProvider for the given domain. If the customer provided one, it is used. + * Otherwise, a UrlJwkProvider is auto-created and cached per domain. + */ + private JwkProvider getJwkProvider(String domain) { + if (jwkProvider != null) { + return jwkProvider; + } + return jwkProviders.computeIfAbsent(domain, d -> new UrlJwkProvider(d)); } List getResponseType() { diff --git a/src/main/java/com/auth0/SignatureVerifier.java b/src/main/java/com/auth0/SignatureVerifier.java deleted file mode 100644 index 3d41df0..0000000 --- a/src/main/java/com/auth0/SignatureVerifier.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.auth0; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.exceptions.JWTDecodeException; -import com.auth0.jwt.exceptions.JWTVerificationException; -import com.auth0.jwt.exceptions.SignatureVerificationException; -import com.auth0.jwt.interfaces.DecodedJWT; -import org.apache.commons.lang3.Validate; - -import java.util.Arrays; -import java.util.List; - -abstract class SignatureVerifier { - - private final JWTVerifier verifier; - private final List acceptedAlgorithms; - - /** - * Creates a new JWT Signature Verifier. - * This instance will validate the token was signed using an expected algorithm - * and then proceed to verify its signature - * - * @param verifier the instance that knows how to verify the signature. When null, the signature will not be checked. - * @param algorithm the accepted algorithms. Must never be null! - */ - SignatureVerifier(JWTVerifier verifier, String... algorithm) { - Validate.notEmpty(algorithm); - this.verifier = verifier; - this.acceptedAlgorithms = Arrays.asList(algorithm); - } - - private DecodedJWT decodeToken(String token) throws TokenValidationException { - try { - return JWT.decode(token); - } catch (JWTDecodeException e) { - throw new TokenValidationException("ID token could not be decoded", e); - } - } - - DecodedJWT verifySignature(String token) throws TokenValidationException { - DecodedJWT decoded = decodeToken(token); - if (!this.acceptedAlgorithms.contains(decoded.getAlgorithm())) { - throw new TokenValidationException(String.format("Signature algorithm of \"%s\" is not supported. Expected the ID token to be signed with \"%s\".", decoded.getAlgorithm(), this.acceptedAlgorithms)); - } - if (verifier != null) { - try { - verifier.verify(decoded); - } catch (SignatureVerificationException e) { - throw new TokenValidationException("Invalid token signature", e); - } catch (JWTVerificationException ignored) { - //NO-OP. Will be catch on a different step - //Would only trigger for "expired tokens" (invalid exp) - } - } - - return decoded; - } -} diff --git a/src/main/java/com/auth0/SymmetricSignatureVerifier.java b/src/main/java/com/auth0/SymmetricSignatureVerifier.java deleted file mode 100644 index 81b013f..0000000 --- a/src/main/java/com/auth0/SymmetricSignatureVerifier.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.auth0; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; - -@SuppressWarnings("unused") -class SymmetricSignatureVerifier extends SignatureVerifier { - - SymmetricSignatureVerifier(String secret) { - super(createJWTVerifier(secret), "HS256"); - } - - private static JWTVerifier createJWTVerifier(String secret) { - Algorithm alg = Algorithm.HMAC256(secret); - return JWT.require(alg) - .ignoreIssuedAt() - .build(); - } -} diff --git a/src/main/java/com/auth0/TokenValidationException.java b/src/main/java/com/auth0/TokenValidationException.java deleted file mode 100644 index e0616ac..0000000 --- a/src/main/java/com/auth0/TokenValidationException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.auth0; - -class TokenValidationException extends RuntimeException { - - TokenValidationException(String message) { - super(message); - } - - TokenValidationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/test/java/com/auth0/IdTokenVerifierTest.java b/src/test/java/com/auth0/IdTokenVerifierTest.java deleted file mode 100644 index ced54c7..0000000 --- a/src/test/java/com/auth0/IdTokenVerifierTest.java +++ /dev/null @@ -1,680 +0,0 @@ -package com.auth0; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.interfaces.DecodedJWT; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Calendar; -import java.util.Date; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class IdTokenVerifierTest { - - private final static String DOMAIN = "tokens-test.auth0.com"; - private final static String AUDIENCE = "tokens-test-123"; - - // Default clock time of September 2, 2019 5:00:00 AM GMT - private final static Date DEFAULT_CLOCK = new Date(1567400400000L); - private final static Integer DEFAULT_CLOCK_SKEW = 60; - - private SignatureVerifier signatureVerifier; - - @BeforeEach - public void setUp() { - signatureVerifier = mock(SignatureVerifier.class); - } - - @Test - public void failsToCreateOptionsWhenIssuerIsNull() { - assertThrows(NullPointerException.class, - () -> new IdTokenVerifier.Options(null, "audience", signatureVerifier)); - } - - @Test - public void failsToCreateOptionsWhenAudienceIsNull() { - assertThrows(NullPointerException.class, - () -> new IdTokenVerifier.Options("issuer", null, signatureVerifier)); - } - - @Test - public void failsToCreateOptionsWhenVerifierIsNull() { - assertThrows(NullPointerException.class, - () -> new IdTokenVerifier.Options("issuer", "audience", null)); - } - - @Test - public void failsWhenIDTokenMissing() { - IdTokenVerifier.Options opts = new IdTokenVerifier.Options("issuer", "audience", signatureVerifier); - IdTokenVerifier verifier = new IdTokenVerifier(); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verify(null, opts)); - assertEquals("ID token is required but missing", e.getMessage()); - } - - @Test - public void failsWhenIDTokenEmpty() { - IdTokenVerifier.Options opts = new IdTokenVerifier.Options("issuer", "audience", signatureVerifier); - IdTokenVerifier verifier = new IdTokenVerifier(); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verify("", opts)); - assertEquals("ID token is required but missing", e.getMessage()); - } - - @Test - public void failsWhenOptionsIsNull() { - assertThrows(NullPointerException.class, () -> new IdTokenVerifier().verify("token", null)); - } - - @Test - public void failsWhenTokenCannotBeDecoded() { - String token = "boom!"; - - SignatureVerifier signatureVerifier = new SymmetricSignatureVerifier("secret"); - IdTokenVerifier.Options opts = new IdTokenVerifier.Options(DOMAIN, AUDIENCE, signatureVerifier); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("ID token could not be decoded", e.getMessage()); - } - - @Test - public void failsWhenSignatureIsInvalid() { - String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.a7ayNmFTxS2D-EIoUikoJ6dck7I8veWyxnje_mYD3qY"; - - SignatureVerifier verifier = new SymmetricSignatureVerifier("asdlk59ckvkr"); - IdTokenVerifier.Options opts = new IdTokenVerifier.Options(DOMAIN, AUDIENCE, verifier); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Invalid token signature", e.getMessage()); - } - - @Test - public void failsWhenIssuerMissing() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.B4PGlucyy-fJ4v5NNK2hntvjAf5m8dJf84WttwVnzV0ZlfPbYUSJm7Vc1ys7iMqXAQzAl2I8bDf2qhtLjaLpDKAH9JUvowUpCL7Bgjd7AEc1Te_IUwwxlpCupgseOEL2nrY8enP6On7BO7BBpngmVwnD1DvuA4lNoaaFyWUopha5Dxd5jw64wMqP4lz13C6Kqs8mINZkkw-NgE8DvWszaXeyPaowy-QpfXmPBnw75YLZlGcjr-WQsWQV7rUezq4Tl_11uPivR-fNcEWdG1mAtsnQnB_zJJKaHYlE0g4fey_6H9FKmCvcNkpBGo9ylbitb7jIuExbFEvEd2r_4wKl0g"; - IdTokenVerifier.Options options = configureOptions(token); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Issuer (iss) claim must be a string present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenIssuerInvalid() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzb21ldGhpbmctZWxzZSIsInN1YiI6ImF1dGgwfDEyMzQ1Njc4OSIsImF1ZCI6WyJ0b2tlbnMtdGVzdC0xMjMiLCJleHRlcm5hbC10ZXN0LTEyMyJdLCJleHAiOjE1Njc0ODY4MDAsImlhdCI6MTU2NzMxNDAwMCwibm9uY2UiOiJhNTl2azU5MiIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU2NzMxNDAwMH0.lHFHyg1ei3hK2vB7X1xB9nqksAEnxtv2KKpE_Gih6RezTruF9uZu1PAZTEwxhfj2UrQxwLqCb-t6wyVnxVpCsymSCq9JIiCVgg_cYV38siMs38N9y26BrVeyifj_VOP9Om_vI_hHjOzhi8WmysK2KKAQnn0skKAkq8epY4axCX3NkRaEIMhhTaITYia3GbJ5Qki8WDD9UVucUVOhgSZBV5p1dL39FKgc9k1MOVZJG-zAd_r5GsUIRk-xUwNX0WYwCR9sC2G-FjJTvlFph_4vksponoUWJ-LPTLM0RwGgmEUPhhnIG23UjsNwpnElY4gWfIL0hsO98-5DpGjn8Ejr0w"; - IdTokenVerifier.Options options = configureOptions(token); - - String errorMessage = String.format("Issuer (iss) claim mismatch in the ID token, expected \"%s\", found \"%s\"", - "https://" + DOMAIN + "/", "something-else"); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(errorMessage, e.getMessage()); - } - - @Test - public void failsWhenSubMissing() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.fDR9NSbbt75w9nzhL-eBfGjOp16HP2vfnO6m_Oav0xrmmgyYsBZSLOPd2C0O46bp6_2hKjeOUhnwYwjocsdXI4hvfQkyACERtneCkwHwSZPZK-1h6vgGF7b_7ILUywEcgo7F6e1qgFTM93Prqk63cCP53KgOBPyx02y0rqkhUOApCWRVBFrfP92tXvFN7E2phmpf9G68PPjwnEvvQtYOGjvFkaWSja7MKT98f7OxgbenBI_mAZy9LmOqUl3SKJOBe5Fibs1snI0l4nzrgQ1GNxVwyfHOdyq-srdGe8rlFx5kdhWh313EOzWxxGTg4RhGY7Tiz1QWago0VQ5yOt0w8A"; - IdTokenVerifier.Options options = configureOptions(token); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Subject (sub) claim must be a string present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenAudienceMissing() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJleHAiOjE1Njc0ODY4MDAsImlhdCI6MTU2NzMxNDAwMCwibm9uY2UiOiJhNTl2azU5MiIsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU2NzMxNDAwMH0.XM-IM9CIZ2cJpZZaKooMSmNgvwHPTse6kcIOPATgewRZxrDdCEjtPHmzmSuyDGy84vSR__DJS_kM2jWWwbkjB_PahXes210dpUqitRW3is9xV0-k0LkVwxmhHCM-e9sClbTbcs4zLv6WWFRq4UEU5DU6HhuHLQeeH0eO2Nv_tkvu-JdpmoepHPjW3ecMs0lhzXRT6_2o-ErTPdYt4W6yqpBG57HRIMzs9F72AWcPC6vhLY0IhMqXaq68Ma3jnEPIXUmv52bll0PuQVBqKd-eDH_jD0ZHFUCkwbfWPrkhJz5Q5qLzSzUjnrWKA3KgP4_Z1KfHY2-nQA2ynMgNFSn_eA"; - IdTokenVerifier.Options options = configureOptions(token); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Audience (aud) claim must be a string or array of strings present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenAudienceDoesNotContainClientId() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOiJleHRlcm5hbC10ZXN0LTEyMyIsImV4cCI6MTU2NzQ4NjgwMCwiaWF0IjoxNTY3MzE0MDAwLCJub25jZSI6ImE1OXZrNTkyIiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTY3MzE0MDAwfQ.SxeNIhm8reywgtSSkZ6jCpbZ8KyC09couFjpcrJFktAYKmJZnGQkv0gQLNUuGejORvysznOlhfO2nkF10yT6pKBiye9xZ8TstWQBorDKHL-74n6ZAxjPg1F0vHNokZq0zpPkwV-gKIFY6aPw3vyZTxzR6CMyoJdwc19A0RXPzPt6T7csQeqX0lzGEqqeIbU4VI5XM5RG1VvN82CgTlOQXlFZrKhyJx_xwslyWWDzx7tpPNid1wusvfznTGxoWO2wUBCyW6EhmyHp2euFi1gdJqHQVbrydutPtQ-FGQEwyWACNN8kBWqQ7UEbqimg6C0NTGrRkkKkJ79DmiW7aULHZQ"; - IdTokenVerifier.Options options = configureOptions(token); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(String.format("Audience (aud) claim mismatch in the ID token; expected \"%s\" but found \"%s\"", AUDIENCE, "[external-test-123]"), e.getMessage()); - } - - @Test - public void failsWhenExpClaimMissing() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiaWF0IjoxNTY3MzE0MDAwLCJub25jZSI6ImE1OXZrNTkyIiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTY3MzE0MDAwfQ.b6saYAZCnCSzpVO0nrAUKVSC1n3GoqUfwrjOXG5gVxda0oFohpYJe68QwzsTmS4fOm7JtbN1FqjVRN6S4i-BnH-XGnciGOMFF4EfaOzsgo7DCrrLrjfx6rmqW8UPYalbfJTQL8mXYnLOxzMGP3DEXNlk-41GSZoFujwTAIqYjrV_Y3MUGYmzcVxdL_h2psLm_p07knMLCm7Cuo8znzKrU4PtuaLflvzorg57S4BD79oLv4uv0_dmhwPUgJDvqWeicR5Qry4aX2L5BT6V-nBWAcu3qVZDymSKcjtTebxszxY1siyA7BQe88ZmgP1bW1KXtMk_fOGsgWHFdu_AH77yow"; - IdTokenVerifier.Options options = configureOptions(token); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Expiration Time (exp) claim must be a number present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenExpClaimInvalidOutsideDefaultLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3MzE0MDAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.uDn-4wtiigGddUw2kis_QyfDE3w75rWvu9NolMgD3b7l4_fedhQOk-z_mYID588ZXpnpLRKKiD5I2IFsXl7Qcc10rx1LIZxNqdzyc3VrgFf677x7fFZ4guR2WalH-zdJEluruMRdCIFQczIjXnGKPHGQ8gPH1LRozv43dl-bO2viX6MU4pTgNq3GIsU4ureyHrx1o9JSqF4b_RzuYvVWVVX7ABC2csMJP_ocVbEIQjUBhp1V7VcQY-Zgq0prk_HvY13g8FxK4KvSza637ZWAfonn599SKuy22PeMJqDfd64SbunWrt-mKBz9PHeAo9t4LJPLsAqSd3IQ2aJTsnqJRA"; - - Integer actualExpTime = 1567314000; - - // set clock to September 1, 2019 5:00:00 AM GMT - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((DEFAULT_CLOCK_SKEW + 1) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClock(clock); - - String errorMessage = String.format("Expiration Time (exp) claim error in the ID token; current time (%d) is after expiration time (%d)", - clock.getTime() / 1000, actualExpTime + DEFAULT_CLOCK_SKEW); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(errorMessage, e.getMessage()); - } - - @Test - public void succeedsWhenExpClaimInPastButWithinDefaultLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3MzE0MDAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.uDn-4wtiigGddUw2kis_QyfDE3w75rWvu9NolMgD3b7l4_fedhQOk-z_mYID588ZXpnpLRKKiD5I2IFsXl7Qcc10rx1LIZxNqdzyc3VrgFf677x7fFZ4guR2WalH-zdJEluruMRdCIFQczIjXnGKPHGQ8gPH1LRozv43dl-bO2viX6MU4pTgNq3GIsU4ureyHrx1o9JSqF4b_RzuYvVWVVX7ABC2csMJP_ocVbEIQjUBhp1V7VcQY-Zgq0prk_HvY13g8FxK4KvSza637ZWAfonn599SKuy22PeMJqDfd64SbunWrt-mKBz9PHeAo9t4LJPLsAqSd3IQ2aJTsnqJRA"; - - // set clock to September 1, 2019 5:00:00 AM GMT - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((DEFAULT_CLOCK_SKEW - 1) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClock(clock); - - new IdTokenVerifier().verify(token, options); - } - - @Test - public void failsWhenExpClaimInvalidOutsideCustomLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3MzE0MDAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.uDn-4wtiigGddUw2kis_QyfDE3w75rWvu9NolMgD3b7l4_fedhQOk-z_mYID588ZXpnpLRKKiD5I2IFsXl7Qcc10rx1LIZxNqdzyc3VrgFf677x7fFZ4guR2WalH-zdJEluruMRdCIFQczIjXnGKPHGQ8gPH1LRozv43dl-bO2viX6MU4pTgNq3GIsU4ureyHrx1o9JSqF4b_RzuYvVWVVX7ABC2csMJP_ocVbEIQjUBhp1V7VcQY-Zgq0prk_HvY13g8FxK4KvSza637ZWAfonn599SKuy22PeMJqDfd64SbunWrt-mKBz9PHeAo9t4LJPLsAqSd3IQ2aJTsnqJRA"; - Integer leeway = 120; - - Date actualExp = JWT.decode(token).getExpiresAt(); - - // set clock to September 1, 2019 5:00:00 AM GMT - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((leeway + 1) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClockSkew(leeway); - options.setClock(clock); - - String errorMessage = String.format("Expiration Time (exp) claim error in the ID token; current time (%d) is after expiration time (%d)", - clock.getTime() / 1000, ((actualExp.getTime() / 1000) + leeway)); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(errorMessage, e.getMessage()); - } - - @Test - public void succeedsWhenExpClaimInPastButWithinCustomLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3MzE0MDAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.uDn-4wtiigGddUw2kis_QyfDE3w75rWvu9NolMgD3b7l4_fedhQOk-z_mYID588ZXpnpLRKKiD5I2IFsXl7Qcc10rx1LIZxNqdzyc3VrgFf677x7fFZ4guR2WalH-zdJEluruMRdCIFQczIjXnGKPHGQ8gPH1LRozv43dl-bO2viX6MU4pTgNq3GIsU4ureyHrx1o9JSqF4b_RzuYvVWVVX7ABC2csMJP_ocVbEIQjUBhp1V7VcQY-Zgq0prk_HvY13g8FxK4KvSza637ZWAfonn599SKuy22PeMJqDfd64SbunWrt-mKBz9PHeAo9t4LJPLsAqSd3IQ2aJTsnqJRA"; - Integer leeway = 120; - - // set clock to September 1, 2019 5:00:00 AM GMTExpiration Time (exp) claim error in the ID token; current time - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((leeway - 1) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClockSkew(leeway); - options.setClock(clock); - new IdTokenVerifier().verify(token, options); - } - - @Test - public void failsWhenIatClaimMissing() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJub25jZSI6ImE1OXZrNTkyIiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTY3MzE0MDAwfQ.SJDgK8W9Y8stMtE9LG2OzHzXzbIDCXg8lRhKyOim4rRXbkg3k0on7gCzN-sy2d5z5TQ-lQzbY3V4z-so3ltVDUYd_8RjmUiKgNK_95UsxfTDM2BlBEQ6USMVl3ojC5jcTBhg5MF16ZBEn94IjIGC9Uks9GPseM-JrtUPx4Uj5VvsBtmeKxLc3rSGt7rYC4JU65Oa-O5pFYRSCbNzRFNHRlmnb5b2uPHxoVLjrJAT0FhlXcsNgfz65MlbXBgAyz7xjCEhw_tTpvptaCwPTeG0mgBYlGQ7Sl3xHJzgG4jLbA7Pvvfcx0MpBPHUZxADh1FFQnf2nHB0ppddiDfOq2mHNA"; - - IdTokenVerifier.Options options = configureOptions(token); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Issued At (iat) claim must be a number present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenNonceConfiguredButNoNonceClaimSent() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsImF6cCI6InRva2Vucy10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU2NzMxNDAwMH0.ZRYK4s72pKXJUSadByPp_MNyuaACmPCyj9RaIfxuTTLXE45YJ0toLK6XjjDv_861E_fRmEKMthnJAmHcKXiDWGb73l3iDtD7clockBOo3KJO2cwkM1uYNpG1kbNkg6WDvgGlVsC7buxr8dbL8fI2e0g53Jl48lE9Ohi5Z_7iRmRoVAx5HE60UDfEqFeAKZyu5VsAahp9q3PwhLfaJVDobtAzWP0LcRA3x8FOA0ZdBBNpvRmeBRugU2GQTSDLSMtGzgi5xXUwXly7pr5bX-lIYICU1Q9R5n-8uYlEaFuiaYTqzxY0fmSzzGeFkwrj7b0yTQ2OwAFVT3MWCSbvjKsy-JWQ"; - IdTokenVerifier.Options options = configureOptions(token); - options.setNonce("kssllk59akth"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Nonce (nonce) claim must be a string present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenNonceIsInvalid() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiMDAwOTk5IiwiYXpwIjoidG9rZW5zLXRlc3QtMTIzIiwiYXV0aF90aW1lIjoxNTY3MzE0MDAwfQ.n4jIX01mNucMs92F8IZtKJeCvgUYPwrrOsaZX91fnzVkDC5tAqi4HLRGHjtUJe1PwmIijJk63FskeuApVPfxfAbITL1KBVDHiin2RVeDSAl5lhSnsSYW-k5MfzXx11MJxhS_VD5zvOgbWmuRYUHlc1zh48YyJZQE-OaEFvxGyyEM7Zhgzfz4D5_kjd2qV890WsXGs_GadyzxATfP59XENnPzMo3VLXyBC4cQ0e7rzBIqquBKo9-MT6rhy_qSwMrZJhyzSzE5gTtMd2Od9YgPUtLznBt34rBD1uJaSs_a4s1Ox3h4jTCm85xWFabGx3kz7xkD33nCiMKQ_FSy1d-toQ"; - - String expectedNonce = "nonce"; - String actualNonce = "000999"; - - IdTokenVerifier.Options options = configureOptions(token); - options.setNonce("nonce"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(String.format("Nonce (nonce) claim mismatch in the ID token; expected \"%s\", found \"%s\"", expectedNonce, actualNonce), e.getMessage()); - } - - @Test - public void failsWhenAudClaimHasMultipleItemsButAzpMissing() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOjQyLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.SliF71jOX9JsGeUPCySf3ucY_tGr3uh183cbcUN9ze3qRiOAc5bi7vdsBtODtlVJgsx0Elt0JrISTJ8SoNkpA4SxrjFpxSsfzPBwQtJrlg7pqflgBH7g6zKGVGRs2Z0jxZaCvXQvRuUZRZwFIncZ2zTFIDI3X5xLeJAGRGWaInOvLLlumGzWzfNLUG_G5uHZQW6sRgyIw9qrdqEWXO6sGjOBG9Au6jIo2IH0I53-UujAnNHWeJRPsM5xw2bHPteIde1xn4N0w26BlZ4GEQifVQDFw3ukah35SQ-ENMMS58Siu-sysF5F3oxdwVaMidyYgrD2VUN_iXIaMPwA2i0M5Q"; - - IdTokenVerifier.Options options = configureOptions(token); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values", e.getMessage()); - } - - @Test - public void failsWhenAudClaimHasMultipleItemsButAzpInvalid() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJleHRlcm5hbC10ZXN0LTEyMyIsImF1dGhfdGltZSI6MTU2NzMxNDAwMH0.GLuChuSum2S6h79rfRbJrJfe_7Fw_D6RHXj9zrAhixoNLMyBosO2GBPsOgoaLTDMonJzCyqskjan-w-SJ5nw7fUmDkWfPVjXcS0x5pt72j0dgfLMu6eOFIA9jWHWN4hsN3XKJktZ9202AohI8fXO5BYQ-jMi0HWQaiUj3f6wITHEN6fTydLo_t24hriExkO1670AgzM22BVTfb-JJlrs32t6ffY77zrF5ahIg_h4ROgrcf_3LejF7ZnubHbpJ-wX-byxW9YXT5tN_JjD5EP6jC37s9iL8ArGEZtBzHVfCO0kqlaH-9PVZXgz8SjMSJ8iA2fXXN0L35ySdzida3hhzw"; - - String actualAzp = "external-test-123"; - - IdTokenVerifier.Options options = configureOptions(token); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(String.format("Authorized Party (azp) claim mismatch in the ID token; expected \"%s\", found \"%s\"", AUDIENCE, actualAzp), e.getMessage()); - } - - @Test - public void failsWhenMaxAgeSentButAuthTimeClaimMissing() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMifQ.Gb36qNHgQgac1fXh9AHX7ZMroymT0j4TjNol3ZirbIOyxuHV4OxCbGcoAAxC8Zt_dIc3DH9SX3QUIwTkE3DsFxS-VJ58R2d9RbXJl5p8pO1sJNFjo59njLKbiBxVil4z8PUsw77c_4f2QtKn6LHzhGqL9CS84LUCgNPPBsBHYyNRJDwIauPrrLyOsZAS3dWlZiUDBFurSYe0Y-O6d8zF_uKOcTD8A2E3SQQlZJQ12T94IprQ9V0tbbWI8VSGQ23JghR62QwZC-rBOF9pQMcLLCNRLFTTF9sXqZuS9XRv7PZ6rRjaonHDWn8WqGjSleWSycPsvwvjjSUVR8Z3iDBZig"; - - IdTokenVerifier.Options options = configureOptions(token); - options.setMaxAge(200); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals("Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified", e.getMessage()); - } - - @Test - public void failsWhenMaxSentButAuthTimeInvalidWithinLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.AbSYZ_Tu0-ZelCRPuu9jOd9y1M19yIlk8bjSQDVVgAekRZLdRA_T_gi_JeWyFysKZVpRcHC1YJhTH4YH8CCMRTwviq3woIsLmdUecjydyZkHcUlhHXj2DbC15cyELalPNe3T9eZ4ySwk9qRJSOkjBAgXAT0a7M6rwri6QHnL0WxTLX4us4rGu8Ui3kuf1WaZH9DNoeWYs1N3xUclockTkRKaqXnuKjnwSVmsuwxFSlnIPJOiMUUZksiaBq_OUvOkB-dEG7OFiDX9XWj1m62yBHkvZHun8LBr9VW3mt1IrcBdbbtzjWwfn6ioK2c4dbtPFhuYohXsmRDaSekP63Dmlw3A"; - - int actualAuthTime = 1567314000; - Integer maxAge = 120; - - // set clock to September 1, 2019 5:00:00 AM GMT - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((maxAge + (DEFAULT_CLOCK_SKEW + 1)) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClock(clock); - options.setMaxAge(maxAge); - - String errorMessage = String.format("Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (%d) is after last auth at (%d)", - clock.getTime() / 1000, actualAuthTime + maxAge + DEFAULT_CLOCK_SKEW); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(errorMessage, e.getMessage()); - } - - @Test - public void succeedsWhenMaxSentAndAuthTimeWithinLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.AbSYZ_Tu0-ZelCRPuu9jOd9y1M19yIlk8bjSQDVVgAekRZLdRA_T_gi_JeWyFysKZVpRcHC1YJhTH4YH8CCMRTwviq3woIsLmdUecjydyZkHcUlhHXj2DbC15cyELalPNe3T9eZ4ySwk9qRJSOkjBAgXAT0a7M6rwri6QHnL0WxTLX4us4rGu8Ui3kuf1WaZH9DNoeWYs1N3xUclockTkRKaqXnuKjnwSVmsuwxFSlnIPJOiMUUZksiaBq_OUvOkB-dEG7OFiDX9XWj1m62yBHkvZHun8LBr9VW3mt1IrcBdbbtzjWwfn6ioK2c4dbtPFhuYohXsmRDaSekP63Dmlw3A"; - - Integer maxAge = 120; - - // set clock to September 1, 2019 5:00:00 AM GMT - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((maxAge + (DEFAULT_CLOCK_SKEW - 1)) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClock(clock); - options.setMaxAge(maxAge); - - new IdTokenVerifier().verify(token, options); - } - - @Test - public void failsWhenMaxSentButAuthTimeInvalidWithCustomLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.AbSYZ_Tu0-ZelCRPuu9jOd9y1M19yIlk8bjSQDVVgAekRZLdRA_T_gi_JeWyFysKZVpRcHC1YJhTH4YH8CCMRTwviq3woIsLmdUecjydyZkHcUlhHXj2DbC15cyELalPNe3T9eZ4ySwk9qRJSOkjBAgXAT0a7M6rwri6QHnL0WxTLX4us4rGu8Ui3kuf1WaZH9DNoeWYs1N3xUclockTkRKaqXnuKjnwSVmsuwxFSlnIPJOiMUUZksiaBq_OUvOkB-dEG7OFiDX9XWj1m62yBHkvZHun8LBr9VW3mt1IrcBdbbtzjWwfn6ioK2c4dbtPFhuYohXsmRDaSekP63Dmlw3A"; - - int actualAuthTime = 1567314000; - Integer maxAge = 120; - Integer customLeeway = 120; - - // set clock to September 1, 2019 5:00:00 AM GMT - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((maxAge + customLeeway + 1) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClock(clock); - options.setMaxAge(maxAge); - options.setClockSkew(customLeeway); - - String errorMessage = String.format("Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (%d) is after last auth at (%d)", - clock.getTime() / 1000, actualAuthTime + maxAge + customLeeway); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, options)); - assertEquals(errorMessage, e.getMessage()); - } - - @Test - public void succeedsWhenMaxSentAndAuthTimeWithCustomLeeway() { - String token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHwxMjM0NTY3ODkiLCJhdWQiOlsidG9rZW5zLXRlc3QtMTIzIiwiZXh0ZXJuYWwtdGVzdC0xMjMiXSwiZXhwIjoxNTY3NDg2ODAwLCJpYXQiOjE1NjczMTQwMDAsIm5vbmNlIjoiYTU5dms1OTIiLCJhenAiOiJ0b2tlbnMtdGVzdC0xMjMiLCJhdXRoX3RpbWUiOjE1NjczMTQwMDB9.AbSYZ_Tu0-ZelCRPuu9jOd9y1M19yIlk8bjSQDVVgAekRZLdRA_T_gi_JeWyFysKZVpRcHC1YJhTH4YH8CCMRTwviq3woIsLmdUecjydyZkHcUlhHXj2DbC15cyELalPNe3T9eZ4ySwk9qRJSOkjBAgXAT0a7M6rwri6QHnL0WxTLX4us4rGu8Ui3kuf1WaZH9DNoeWYs1N3xUclockTkRKaqXnuKjnwSVmsuwxFSlnIPJOiMUUZksiaBq_OUvOkB-dEG7OFiDX9XWj1m62yBHkvZHun8LBr9VW3mt1IrcBdbbtzjWwfn6ioK2c4dbtPFhuYohXsmRDaSekP63Dmlw3A"; - - Integer maxAge = 120; - Integer customLeeway = 120; - - // set clock to September 1, 2019 5:00:00 AM GMT - Date clock = new Date(1567314000000L); - clock.setTime(clock.getTime() + ((maxAge + customLeeway - 1) * 1000)); - - IdTokenVerifier.Options options = configureOptions(token); - options.setClock(clock); - options.setMaxAge(maxAge); - options.setClockSkew(customLeeway); - - new IdTokenVerifier().verify(token, options); - } - - @Test - public void succeedsWithValidTokenUsingDefaultClock() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("nonce", "nonce") - .sign(Algorithm.HMAC256("secret")); - - DecodedJWT decodedJWT = JWT.decode(token); - SignatureVerifier verifier = mock(SignatureVerifier.class); - when(verifier.verifySignature(token)).thenReturn(decodedJWT); - - IdTokenVerifier.Options opts = new IdTokenVerifier.Options("https://" + DOMAIN + "/", AUDIENCE, verifier); - opts.setNonce("nonce"); - - new IdTokenVerifier().verify(token, opts); - } - - @Test - public void succeedsWithValidTokenUsingDefaultClockAndHttpDomain() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("http://" + DOMAIN + "/") - .withClaim("nonce", "nonce") - .sign(Algorithm.HMAC256("secret")); - - DecodedJWT decodedJWT = JWT.decode(token); - SignatureVerifier verifier = mock(SignatureVerifier.class); - when(verifier.verifySignature(token)).thenReturn(decodedJWT); - - IdTokenVerifier.Options opts = new IdTokenVerifier.Options("http://" + DOMAIN + "/", AUDIENCE, verifier); - opts.setNonce("nonce"); - - new IdTokenVerifier().verify(token, opts); - } - - @Test - public void succeedsWithValidTokenUsingDefaultClockAndHttpsDomain() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("nonce", "nonce") - .sign(Algorithm.HMAC256("secret")); - - DecodedJWT decodedJWT = JWT.decode(token); - SignatureVerifier verifier = mock(SignatureVerifier.class); - when(verifier.verifySignature(token)).thenReturn(decodedJWT); - - IdTokenVerifier.Options opts = new IdTokenVerifier.Options("https://" + DOMAIN + "/", AUDIENCE, verifier); - opts.setNonce("nonce"); - - new IdTokenVerifier().verify(token, opts); - } - - @Test - public void succeedsWhenOrganizationNameMatchesExpected() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_name", "my org") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("my org"); - - new IdTokenVerifier().verify(token, opts); - } - - @Test - public void failsWhenOrganizationNameDoesNotMatchExpected() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_name", "my org") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("other org"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Organization (org_name) claim mismatch in the ID token; expected \"other org\" but found \"my org\"", e.getMessage()); - } - - @Test - public void succeedsWhenOrganizationNameDoesNotMatchExpected_caseInsensitive() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_name", "my org") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("My org"); - - new IdTokenVerifier().verify(token, opts); - } - - @Test - public void failsWhenOrganizationNameExpectedButNotPresent() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("my org"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Organization name (org_name) claim must be a string present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenOrganizationNameExpectedButClaimIsNotString() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_name", 42) - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("my org"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Organization name (org_name) claim must be a string present in the ID token", e.getMessage()); - } - - @Test - public void succeedsWhenOrganizationNameNotSpecifiedButIsPresent() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_name", "my org") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - new IdTokenVerifier().verify(token, opts); - } - - @Test - public void succeedsWhenOrganizationIdMatchesExpected() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_id", "org_123") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("org_123"); - - new IdTokenVerifier().verify(token, opts); - } - - @Test - public void failsWhenOrganizationIdDoesNotMatchExpected() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_id", "org_123") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("org_abc"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Organization (org_id) claim mismatch in the ID token; expected \"org_abc\" but found \"org_123\"", e.getMessage()); - } - - @Test - public void failsWhenOrganizationIdDoesNotMatchExpected_caseSensitive() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_id", "org_123") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("org_aBc"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Organization (org_id) claim mismatch in the ID token; expected \"org_aBc\" but found \"org_123\"", e.getMessage()); - } - - @Test - public void failsWhenOrganizationIdExpectedButNotPresent() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("org_123"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Organization Id (org_id) claim must be a string present in the ID token", e.getMessage()); - } - - @Test - public void failsWhenOrganizationIdExpectedButClaimIsNotString() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_id", 42) - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - opts.setOrganization("org_123"); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> new IdTokenVerifier().verify(token, opts)); - assertEquals("Organization Id (org_id) claim must be a string present in the ID token", e.getMessage()); - } - - @Test - public void succeedsWhenOrganizationIdNotSpecifiedButIsPresent() { - String token = JWT.create() - .withSubject("auth0|sdk458fks") - .withAudience(AUDIENCE) - .withIssuedAt(getYesterday()) - .withExpiresAt(getTomorrow()) - .withIssuer("https://" + DOMAIN + "/") - .withClaim("org_id", "org_123") - .sign(Algorithm.HMAC256("secret")); - - String jwt = JWT.decode(token).getToken(); - - IdTokenVerifier.Options opts = configureOptions(jwt); - new IdTokenVerifier().verify(token, opts); - } - - private IdTokenVerifier.Options configureOptions(String token) { - DecodedJWT decodedJWT = JWT.decode(token); - SignatureVerifier verifier = mock(SignatureVerifier.class); - when(verifier.verifySignature(token)).thenReturn(decodedJWT); - - IdTokenVerifier.Options opts = new IdTokenVerifier.Options("https://" + DOMAIN + "/", AUDIENCE, verifier); - opts.setClock(DEFAULT_CLOCK); - return opts; - } - - private Date getYesterday() { - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.DATE, -1); - - return cal.getTime(); - } - - private Date getTomorrow() { - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.DATE, 1); - - return cal.getTime(); - } -} diff --git a/src/test/java/com/auth0/RequestProcessorTest.java b/src/test/java/com/auth0/RequestProcessorTest.java index c719dea..ec30c56 100644 --- a/src/test/java/com/auth0/RequestProcessorTest.java +++ b/src/test/java/com/auth0/RequestProcessorTest.java @@ -3,6 +3,7 @@ import com.auth0.client.auth.AuthAPI; import com.auth0.exception.Auth0Exception; import com.auth0.json.auth.TokenHolder; +import com.auth0.jwk.JwkProvider; import com.auth0.net.Response; import com.auth0.net.TokenRequest; import org.junit.jupiter.api.BeforeEach; @@ -41,7 +42,7 @@ public class RequestProcessorTest { @Mock private DomainProvider mockDomainProvider; @Mock - private SignatureVerifier mockSignatureVerifier; + private JwkProvider mockJwkProvider; @Mock private AuthAPI mockAuthAPI; @Mock @@ -70,8 +71,7 @@ public void shouldBuildRequestProcessorWithRequiredParameters() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .build(); assertThat(processor, is(notNullValue())); @@ -83,8 +83,8 @@ public void shouldBuildRequestProcessorWithAllOptionalParameters() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) + .withJwkProvider(mockJwkProvider) .withClockSkew(120) .withAuthenticationMaxAge(3600) .withCookiePath("/custom") @@ -111,8 +111,7 @@ public void shouldDisableLegacySameSiteCookie() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withLegacySameSiteCookie(false) .build(); @@ -373,20 +372,20 @@ public void shouldThrowOnProcessIfCodeRequestSucceedsButDoesNotPassIdTokenVerifi MockHttpServletRequest request = getRequest(params); request.setCookies(new Cookie("com.auth0.state", "1234")); - // Return a structurally valid JWT with wrong issuer so verification fails + // Return a structurally valid JWT with invalid signature so verification fails String fakeJwt = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3dyb25nLyIsInN1YiI6InVzZXIxMjMiLCJhdWQiOiJ0ZXN0Q2xpZW50SWQiLCJleHAiOjk5OTk5OTk5OTksImlhdCI6MTYwMDAwMDAwMH0.signature"; when(mockTokenHolder.getIdToken()).thenReturn(fakeJwt); when(mockTokenResponse.getBody()).thenReturn(mockTokenHolder); when(mockTokenRequest.execute()).thenReturn(mockTokenResponse); when(mockAuthAPI.exchangeCode(eq("abc123"), anyString())).thenReturn(mockTokenRequest); - // Use real AlgorithmNameVerifier so signature check passes but claim validation fails + // Use mockJwkProvider — token has invalid signature so RS256 verification will fail RequestProcessor handler = new RequestProcessor.Builder( mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - new AlgorithmNameVerifier()) + CLIENT_SECRET) + .withJwkProvider(mockJwkProvider) .build(); RequestProcessor spy = spy(handler); doReturn(mockAuthAPI).when(spy).createClientForDomain(anyString()); @@ -461,7 +460,7 @@ public void shouldReturnEmptyTokensWhenCodeRequestReturnsNoTokens() throws Excep public void shouldThrowOnProcessIfIdTokenRequestDoesNotPassIdTokenVerification() { when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); - // Structurally valid JWT with wrong issuer so claim validation fails + // Structurally valid JWT with invalid signature so verification fails String fakeJwt = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3dyb25nLyIsInN1YiI6InVzZXIxMjMiLCJhdWQiOiJ0ZXN0Q2xpZW50SWQiLCJleHAiOjk5OTk5OTk5OTksImlhdCI6MTYwMDAwMDAwMH0.signature"; Map params = new HashMap<>(); @@ -470,13 +469,13 @@ public void shouldThrowOnProcessIfIdTokenRequestDoesNotPassIdTokenVerification() MockHttpServletRequest request = getRequest(params); request.setCookies(new Cookie("com.auth0.state", "1234")); - // Use real AlgorithmNameVerifier so signature check passes but claim validation fails + // Use mockJwkProvider — token has invalid signature so RS256 verification will fail RequestProcessor handler = new RequestProcessor.Builder( mockDomainProvider, RESPONSE_TYPE_ID_TOKEN, CLIENT_ID, - CLIENT_SECRET, - new AlgorithmNameVerifier()) + CLIENT_SECRET) + .withJwkProvider(mockJwkProvider) .build(); IdentityVerificationException e = assertThrows(IdentityVerificationException.class, () -> handler.process(request, response)); @@ -506,8 +505,7 @@ public void shouldBuildAuthorizeUrlWithOrganization() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withOrganization("org_123") .build(); @@ -526,8 +524,7 @@ public void shouldBuildAuthorizeUrlWithInvitation() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withInvitation("inv_456") .build(); @@ -546,8 +543,7 @@ public void shouldBuildAuthorizeUrlWithCustomCookiePath() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withCookiePath("/custom") .build(); @@ -591,8 +587,7 @@ public void shouldSupportOrganizationParameter() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withOrganization("org_123") .build(); @@ -605,8 +600,7 @@ public void shouldSupportInvitationParameter() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withInvitation("inv_456") .build(); @@ -619,8 +613,7 @@ public void shouldSupportCustomCookiePath() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withCookiePath("/custom/path") .build(); @@ -633,8 +626,7 @@ public void shouldSupportClockSkewConfiguration() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withClockSkew(180) .build(); @@ -647,8 +639,7 @@ public void shouldSupportAuthenticationMaxAge() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) .withAuthenticationMaxAge(7200) .build(); @@ -662,8 +653,8 @@ private RequestProcessor createDefaultRequestProcessor() { mockDomainProvider, RESPONSE_TYPE_CODE, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) + .withJwkProvider(mockJwkProvider) .build(); } @@ -672,8 +663,8 @@ private RequestProcessor createRequestProcessorWithResponseType(String responseT mockDomainProvider, responseType, CLIENT_ID, - CLIENT_SECRET, - mockSignatureVerifier) + CLIENT_SECRET) + .withJwkProvider(mockJwkProvider) .build(); } diff --git a/src/test/java/com/auth0/SignatureVerifierTest.java b/src/test/java/com/auth0/SignatureVerifierTest.java deleted file mode 100644 index b0b6678..0000000 --- a/src/test/java/com/auth0/SignatureVerifierTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.auth0; - -import com.auth0.jwk.Jwk; -import com.auth0.jwk.JwkException; -import com.auth0.jwk.JwkProvider; -import com.auth0.jwt.interfaces.DecodedJWT; -import org.junit.jupiter.api.Test; - -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Paths; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; -import java.util.Scanner; - -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class SignatureVerifierTest { - - private static final String EXPIRED_HS_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIiwiZXhwIjo5NzE3ODkzMTd9.5_VOXBmOVMSi8OGgonyfyiJSq3A03PwOEuZlPD-Gxik"; - private static final String NONE_JWT = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0."; - private static final String HS_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.a7ayNmFTxS2D-EIoUikoJ6dck7I8veWyxnje_mYD3qY"; - private static final String RS_JWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYzEyMyJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.PkPWdoZNfXz8EB0SBPH83lNSOhyhdhdqYIgIwgY2nHozUnFOaUjVewlAXxP_3LBGibQ_ng4s5fEEOCJjaKBy04McryvOuL6nqb1dPQseeyxuv2zQitfrs-7kEtfeS3umywM-tV6guw9_W3nmIgaXOiYiF4WJM23ItbdCmvwdXLaf9-xHkQbRY_zEwEFbprFttKUXFbkPt6XjZ3zZwZbNZn64bx2PBiSJ2KMZAE3Lghmci-RXdhi7hXpmN30Tzze1ZsjvVeRRKNzShByKK9ZGZPmQ5yggJOXFy32ehjGkYwFMCqgMQomcGbcYhsd97huKHMHl3HOE5GDYjIq9o9oKRA"; - private static final String RS_PUBLIC_KEY = "src/test/resources/public.pem"; - private static final String RS_PUBLIC_KEY_BAD = "src/test/resources/bad-public.pem"; - private static final String RS_JWT_INVALID_SIGNATURE = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYzEyMyJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.PkPWdoZNfXz8EB0SBPH83lNSOhyhdhdqYIgIwgY2nHozUnFOaUjVewlAXxP_3LBGibQ_ng4s5fEEOCJjaKBy04McryvOuL6nqb1dPQseeyxuv2zQitfrs-7kEtfeS3umywM-tV6guw9_W3nmIgaXOiYiF4WJM23ItbdCmvwdXLaf9-xHkQbRY_zEwEFbprFttKUXFbkPt6XjZ3zZwZbNZn64bx2PBiSJ2KMZAE3Lghmci-RXdhi7hXpmN30Tzze1ZsjvVeRRKNzShByKK9ZGZPmQ5yggJOXFy32ehjGkYwFMCqgMQomcGbcYhsd97huKHMHl3HOE5GDYjIq9o9oABC"; - private static final String HS_JWT_INVALID_SIGNATURE = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjEyMzQiLCJpc3MiOiJodHRwczovL21lLmF1dGgwLmNvbS8iLCJhdWQiOiJkYU9nbkdzUlloa3d1NjIxdmYiLCJzdWIiOiJhdXRoMHx1c2VyMTIzIn0.eTxhYFIHNii1zjxGr9QZvPcqofOd_4bHcjxGq7CQluY"; - - @Test - public void failsWhenAlgorithmIsNotExpected() { - SignatureVerifier verifier = new AlgorithmNameVerifier(); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(NONE_JWT)); - assertEquals("Signature algorithm of \"none\" is not supported. Expected the ID token to be signed with \"[HS256, RS256]\".", e.getMessage()); - } - - @Test - public void failsWhenTokenCannotBeDecoded() { - SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature("boom")); - assertEquals("ID token could not be decoded", e.getMessage()); - } - - @Test - public void failsWhenAlgorithmRS256IsNotExpected() { - SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(RS_JWT)); - assertEquals("Signature algorithm of \"RS256\" is not supported. Expected the ID token to be signed with \"[HS256]\".", e.getMessage()); - } - - @Test - public void failsWhenAlgorithmHS256IsNotExpected() throws Exception { - SignatureVerifier verifier = new AsymmetricSignatureVerifier(getRSProvider(RS_PUBLIC_KEY)); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(HS_JWT)); - assertEquals("Signature algorithm of \"HS256\" is not supported. Expected the ID token to be signed with \"[RS256]\".", e.getMessage()); - } - - @Test - public void succeedsSkippingSignatureCheckOnHS256Token() { - SignatureVerifier verifier = new AlgorithmNameVerifier(); - DecodedJWT decodedJWT1 = verifier.verifySignature(HS_JWT); - DecodedJWT decodedJWT2 = verifier.verifySignature(HS_JWT_INVALID_SIGNATURE); - - assertThat(decodedJWT1, notNullValue()); - assertThat(decodedJWT2, notNullValue()); - } - - @Test - public void succeedsSkippingSignatureCheckOnRS256Token() { - SignatureVerifier verifier = new AlgorithmNameVerifier(); - DecodedJWT decodedJWT1 = verifier.verifySignature(RS_JWT); - DecodedJWT decodedJWT2 = verifier.verifySignature(RS_JWT_INVALID_SIGNATURE); - - assertThat(decodedJWT1, notNullValue()); - assertThat(decodedJWT2, notNullValue()); - } - - @Test - public void succeedsWithValidSignatureHS256Token() { - SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); - DecodedJWT decodedJWT = verifier.verifySignature(HS_JWT); - - assertThat(decodedJWT, notNullValue()); - } - - @Test - public void succeedsAndIgnoresExpiredTokenException() { - SignatureVerifier verifier = new SymmetricSignatureVerifier("secret"); - DecodedJWT decodedJWT = verifier.verifySignature(EXPIRED_HS_JWT); - - assertThat(decodedJWT, notNullValue()); - } - - @Test - public void failsWithInvalidSignatureHS256Token() { - SignatureVerifier verifier = new SymmetricSignatureVerifier("badsecret"); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(HS_JWT)); - assertEquals("Invalid token signature", e.getMessage()); - } - - @Test - public void succeedsWithValidSignatureRS256Token() throws Exception { - SignatureVerifier verifier = new AsymmetricSignatureVerifier(getRSProvider(RS_PUBLIC_KEY)); - DecodedJWT decodedJWT = verifier.verifySignature(RS_JWT); - - assertThat(decodedJWT, notNullValue()); - } - - @Test - public void failsWithInvalidSignatureRS256Token() throws Exception { - SignatureVerifier verifier = new AsymmetricSignatureVerifier(getRSProvider(RS_PUBLIC_KEY_BAD)); - - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(RS_JWT)); - assertEquals("Invalid token signature", e.getMessage()); - } - - @Test - public void failsWhenErrorGettingJwk() throws Exception { - JwkProvider jwkProvider = mock(JwkProvider.class); - when(jwkProvider.get("abc123")).thenThrow(JwkException.class); - - SignatureVerifier verifier = new AsymmetricSignatureVerifier(jwkProvider); - TokenValidationException e = assertThrows(TokenValidationException.class, () -> verifier.verifySignature(RS_JWT)); - assertEquals("Invalid token signature", e.getMessage()); - } - - private JwkProvider getRSProvider(String rsaPath) throws Exception { - JwkProvider jwkProvider = mock(JwkProvider.class); - Jwk jwk = mock(Jwk.class); - when(jwkProvider.get("abc123")).thenReturn(jwk); - RSAPublicKey key = readPublicKeyFromFile(rsaPath); - when(jwk.getPublicKey()).thenReturn(key); - return jwkProvider; - } - - private static RSAPublicKey readPublicKeyFromFile(final String path) throws IOException { - Scanner scanner = null; - BufferedReader reader = null; - try { - scanner = new Scanner(Paths.get(path)); - if (scanner.hasNextLine() && scanner.nextLine().startsWith("-----BEGIN CERTIFICATE-----")) { - FileInputStream fs = new FileInputStream(path); - CertificateFactory fact = CertificateFactory.getInstance("X.509"); - X509Certificate cer = (X509Certificate) fact.generateCertificate(fs); - PublicKey key = cer.getPublicKey(); - fs.close(); - return (RSAPublicKey) key; - } else { - reader = new BufferedReader(new FileReader(path)); - StringBuilder pemContent = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - if (!line.startsWith("-----BEGIN") && !line.startsWith("-----END")) { - pemContent.append(line); - } - } - byte[] keyBytes = Base64.getDecoder().decode(pemContent.toString()); - KeyFactory kf = KeyFactory.getInstance("RSA"); - EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); - return (RSAPublicKey) kf.generatePublic(keySpec); - } - } catch (Exception e) { - throw new IOException("Couldn't parse the RSA Public Key / Certificate file.", e); - } finally { - if (scanner != null) { - scanner.close(); - } - if (reader != null) { - reader.close(); - } - } - } -}