diff --git a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java index 487addaf..a311ab33 100644 --- a/lib/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/lib/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -3,6 +3,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.*; import com.auth0.jwt.impl.JWTParser; +import com.auth0.jwt.impl.NullClaim; import com.auth0.jwt.impl.PublicClaims; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.Clock; @@ -115,6 +116,13 @@ public Verification withJWTId(String jwtId) { return this; } + @Override + public Verification withClaimPresence(String name) throws IllegalArgumentException { + assertNonNull(name); + requireClaim(name, NonEmptyClaim.getInstance()); + return this; + } + @Override public Verification withClaim(String name, Boolean value) throws IllegalArgumentException { assertNonNull(name); @@ -289,35 +297,49 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException { for (Map.Entry entry : claims.entrySet()) { - switch (entry.getKey()) { - case PublicClaims.AUDIENCE: - assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); - break; - case PublicClaims.EXPIRES_AT: - assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); - break; - case PublicClaims.ISSUED_AT: - assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); - break; - case PublicClaims.NOT_BEFORE: - assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); - break; - case PublicClaims.ISSUER: - assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); - break; - case PublicClaims.JWT_ID: - assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); - break; - case PublicClaims.SUBJECT: - assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); - break; - default: - assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); - break; + if (entry.getValue() instanceof NonEmptyClaim) { + assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey()); + } else { + verifyClaimValues(jwt, entry); } } } + private void verifyClaimValues(DecodedJWT jwt, Map.Entry entry) { + switch (entry.getKey()) { + case PublicClaims.AUDIENCE: + assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue()); + break; + case PublicClaims.EXPIRES_AT: + assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true); + break; + case PublicClaims.ISSUED_AT: + assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false); + break; + case PublicClaims.NOT_BEFORE: + assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false); + break; + case PublicClaims.ISSUER: + assertValidIssuerClaim(jwt.getIssuer(), (List) entry.getValue()); + break; + case PublicClaims.JWT_ID: + assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue()); + break; + case PublicClaims.SUBJECT: + assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue()); + break; + default: + assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue()); + break; + } + } + + private void assertClaimPresent(Claim claim, String claimName) { + if (claim instanceof NullClaim) { + throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", claimName)); + } + } + private void assertValidClaim(Claim claim, String claimName, Object value) { boolean isValid = false; if (value instanceof String) { @@ -400,4 +422,20 @@ private void assertValidIssuerClaim(String issuer, List value) { throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer."); } } + + /** + * Simple singleton used to mark that a claim should only be verified for presence. + */ + private static class NonEmptyClaim { + private static NonEmptyClaim nonEmptyClaim; + + private NonEmptyClaim() {} + + public static NonEmptyClaim getInstance() { + if (nonEmptyClaim == null) { + nonEmptyClaim = new NonEmptyClaim(); + } + return nonEmptyClaim; + } + } } diff --git a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java index 72ae35d2..e23f4340 100644 --- a/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java +++ b/lib/src/main/java/com/auth0/jwt/interfaces/Verification.java @@ -80,6 +80,14 @@ public interface Verification { */ Verification withJWTId(String jwtId); + /** + * Require a claim to be present, with any value. + * @param name the Claim's name. + * @return this same Verification instance + * @throws IllegalArgumentException if the name is null. + */ + Verification withClaimPresence(String name) throws IllegalArgumentException; + /** * Require a specific Claim value. * diff --git a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java index d57ca187..156a39a6 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -790,4 +790,145 @@ public void shouldSkipClaimValidationsIfNoClaimsRequired() throws Exception { assertThat(jwt, is(notNullValue())); } + + @Test + public void shouldThrowWhenVerifyingClaimPresenceButClaimNotPresent() { + exception.expect(InvalidClaimException.class); + exception.expectMessage("The Claim 'missing' is not present in the JWT."); + + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("missing") + .build(); + + verifier.verify(jwt); + } + + @Test + public void shouldThrowWhenVerifyingClaimPresenceWhenClaimNameIsNull() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The Custom Claim's name can't be null."); + + String jwt = JWTCreator.init() + .withClaim("custom", "value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence(null); + } + + @Test + public void shouldVerifyStringClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", "") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyBooleanClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", true) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyIntegerClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 123) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyLongClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 922337203685477600L) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyDoubleClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", 12.34) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyListClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", Collections.singletonList("item")) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyMapClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("custom", Collections.singletonMap("key", "value")) + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("custom") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } + + @Test + public void shouldVerifyStandardClaimPresence() { + String jwt = JWTCreator.init() + .withClaim("aud", "any value") + .sign(Algorithm.HMAC256("secret")); + + JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret")) + .withClaimPresence("aud") + .build(); + + DecodedJWT decodedJWT = verifier.verify(jwt); + assertThat(decodedJWT, is(notNullValue())); + } }