Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to verify claim presence #442

Merged
merged 5 commits into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 63 additions & 25 deletions lib/src/main/java/com/auth0/jwt/JWTVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -289,35 +297,49 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws

private void verifyClaims(DecodedJWT jwt, Map<String, Object> claims) throws TokenExpiredException, InvalidClaimException {
for (Map.Entry<String, Object> entry : claims.entrySet()) {
switch (entry.getKey()) {
case PublicClaims.AUDIENCE:
assertValidAudienceClaim(jwt.getAudience(), (List<String>) 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<String>) 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<String, Object> entry) {
switch (entry.getKey()) {
case PublicClaims.AUDIENCE:
assertValidAudienceClaim(jwt.getAudience(), (List<String>) 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<String>) 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) {
Expand Down Expand Up @@ -400,4 +422,20 @@ private void assertValidIssuerClaim(String issuer, List<String> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
128 changes: 128 additions & 0 deletions lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -790,4 +790,132 @@ 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 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()));
}
}