Skip to content

Commit 84c2258

Browse files
authored
Add ability to verify claim presence (#442)
* add ability to verify claim presence only * remove commented-out method * Use singleton class to mark a claim should have only presence checked * minor refactoring to claim verification logic for readability * add test for null claim name
1 parent ac92da2 commit 84c2258

File tree

3 files changed

+212
-25
lines changed

3 files changed

+212
-25
lines changed

lib/src/main/java/com/auth0/jwt/JWTVerifier.java

+63-25
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.auth0.jwt.algorithms.Algorithm;
44
import com.auth0.jwt.exceptions.*;
55
import com.auth0.jwt.impl.JWTParser;
6+
import com.auth0.jwt.impl.NullClaim;
67
import com.auth0.jwt.impl.PublicClaims;
78
import com.auth0.jwt.interfaces.Claim;
89
import com.auth0.jwt.interfaces.Clock;
@@ -115,6 +116,13 @@ public Verification withJWTId(String jwtId) {
115116
return this;
116117
}
117118

119+
@Override
120+
public Verification withClaimPresence(String name) throws IllegalArgumentException {
121+
assertNonNull(name);
122+
requireClaim(name, NonEmptyClaim.getInstance());
123+
return this;
124+
}
125+
118126
@Override
119127
public Verification withClaim(String name, Boolean value) throws IllegalArgumentException {
120128
assertNonNull(name);
@@ -289,35 +297,49 @@ private void verifyAlgorithm(DecodedJWT jwt, Algorithm expectedAlgorithm) throws
289297

290298
private void verifyClaims(DecodedJWT jwt, Map<String, Object> claims) throws TokenExpiredException, InvalidClaimException {
291299
for (Map.Entry<String, Object> entry : claims.entrySet()) {
292-
switch (entry.getKey()) {
293-
case PublicClaims.AUDIENCE:
294-
assertValidAudienceClaim(jwt.getAudience(), (List<String>) entry.getValue());
295-
break;
296-
case PublicClaims.EXPIRES_AT:
297-
assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true);
298-
break;
299-
case PublicClaims.ISSUED_AT:
300-
assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false);
301-
break;
302-
case PublicClaims.NOT_BEFORE:
303-
assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false);
304-
break;
305-
case PublicClaims.ISSUER:
306-
assertValidIssuerClaim(jwt.getIssuer(), (List<String>) entry.getValue());
307-
break;
308-
case PublicClaims.JWT_ID:
309-
assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue());
310-
break;
311-
case PublicClaims.SUBJECT:
312-
assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue());
313-
break;
314-
default:
315-
assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue());
316-
break;
300+
if (entry.getValue() instanceof NonEmptyClaim) {
301+
assertClaimPresent(jwt.getClaim(entry.getKey()), entry.getKey());
302+
} else {
303+
verifyClaimValues(jwt, entry);
317304
}
318305
}
319306
}
320307

308+
private void verifyClaimValues(DecodedJWT jwt, Map.Entry<String, Object> entry) {
309+
switch (entry.getKey()) {
310+
case PublicClaims.AUDIENCE:
311+
assertValidAudienceClaim(jwt.getAudience(), (List<String>) entry.getValue());
312+
break;
313+
case PublicClaims.EXPIRES_AT:
314+
assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true);
315+
break;
316+
case PublicClaims.ISSUED_AT:
317+
assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false);
318+
break;
319+
case PublicClaims.NOT_BEFORE:
320+
assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false);
321+
break;
322+
case PublicClaims.ISSUER:
323+
assertValidIssuerClaim(jwt.getIssuer(), (List<String>) entry.getValue());
324+
break;
325+
case PublicClaims.JWT_ID:
326+
assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue());
327+
break;
328+
case PublicClaims.SUBJECT:
329+
assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue());
330+
break;
331+
default:
332+
assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue());
333+
break;
334+
}
335+
}
336+
337+
private void assertClaimPresent(Claim claim, String claimName) {
338+
if (claim instanceof NullClaim) {
339+
throw new InvalidClaimException(String.format("The Claim '%s' is not present in the JWT.", claimName));
340+
}
341+
}
342+
321343
private void assertValidClaim(Claim claim, String claimName, Object value) {
322344
boolean isValid = false;
323345
if (value instanceof String) {
@@ -400,4 +422,20 @@ private void assertValidIssuerClaim(String issuer, List<String> value) {
400422
throw new InvalidClaimException("The Claim 'iss' value doesn't match the required issuer.");
401423
}
402424
}
425+
426+
/**
427+
* Simple singleton used to mark that a claim should only be verified for presence.
428+
*/
429+
private static class NonEmptyClaim {
430+
private static NonEmptyClaim nonEmptyClaim;
431+
432+
private NonEmptyClaim() {}
433+
434+
public static NonEmptyClaim getInstance() {
435+
if (nonEmptyClaim == null) {
436+
nonEmptyClaim = new NonEmptyClaim();
437+
}
438+
return nonEmptyClaim;
439+
}
440+
}
403441
}

lib/src/main/java/com/auth0/jwt/interfaces/Verification.java

+8
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ public interface Verification {
8080
*/
8181
Verification withJWTId(String jwtId);
8282

83+
/**
84+
* Require a claim to be present, with any value.
85+
* @param name the Claim's name.
86+
* @return this same Verification instance
87+
* @throws IllegalArgumentException if the name is null.
88+
*/
89+
Verification withClaimPresence(String name) throws IllegalArgumentException;
90+
8391
/**
8492
* Require a specific Claim value.
8593
*

lib/src/test/java/com/auth0/jwt/JWTVerifierTest.java

+141
Original file line numberDiff line numberDiff line change
@@ -790,4 +790,145 @@ public void shouldSkipClaimValidationsIfNoClaimsRequired() throws Exception {
790790

791791
assertThat(jwt, is(notNullValue()));
792792
}
793+
794+
@Test
795+
public void shouldThrowWhenVerifyingClaimPresenceButClaimNotPresent() {
796+
exception.expect(InvalidClaimException.class);
797+
exception.expectMessage("The Claim 'missing' is not present in the JWT.");
798+
799+
String jwt = JWTCreator.init()
800+
.withClaim("custom", "")
801+
.sign(Algorithm.HMAC256("secret"));
802+
803+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
804+
.withClaimPresence("missing")
805+
.build();
806+
807+
verifier.verify(jwt);
808+
}
809+
810+
@Test
811+
public void shouldThrowWhenVerifyingClaimPresenceWhenClaimNameIsNull() {
812+
exception.expect(IllegalArgumentException.class);
813+
exception.expectMessage("The Custom Claim's name can't be null.");
814+
815+
String jwt = JWTCreator.init()
816+
.withClaim("custom", "value")
817+
.sign(Algorithm.HMAC256("secret"));
818+
819+
JWTVerifier.init(Algorithm.HMAC256("secret"))
820+
.withClaimPresence(null);
821+
}
822+
823+
@Test
824+
public void shouldVerifyStringClaimPresence() {
825+
String jwt = JWTCreator.init()
826+
.withClaim("custom", "")
827+
.sign(Algorithm.HMAC256("secret"));
828+
829+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
830+
.withClaimPresence("custom")
831+
.build();
832+
833+
DecodedJWT decodedJWT = verifier.verify(jwt);
834+
assertThat(decodedJWT, is(notNullValue()));
835+
}
836+
837+
@Test
838+
public void shouldVerifyBooleanClaimPresence() {
839+
String jwt = JWTCreator.init()
840+
.withClaim("custom", true)
841+
.sign(Algorithm.HMAC256("secret"));
842+
843+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
844+
.withClaimPresence("custom")
845+
.build();
846+
847+
DecodedJWT decodedJWT = verifier.verify(jwt);
848+
assertThat(decodedJWT, is(notNullValue()));
849+
}
850+
851+
@Test
852+
public void shouldVerifyIntegerClaimPresence() {
853+
String jwt = JWTCreator.init()
854+
.withClaim("custom", 123)
855+
.sign(Algorithm.HMAC256("secret"));
856+
857+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
858+
.withClaimPresence("custom")
859+
.build();
860+
861+
DecodedJWT decodedJWT = verifier.verify(jwt);
862+
assertThat(decodedJWT, is(notNullValue()));
863+
}
864+
865+
@Test
866+
public void shouldVerifyLongClaimPresence() {
867+
String jwt = JWTCreator.init()
868+
.withClaim("custom", 922337203685477600L)
869+
.sign(Algorithm.HMAC256("secret"));
870+
871+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
872+
.withClaimPresence("custom")
873+
.build();
874+
875+
DecodedJWT decodedJWT = verifier.verify(jwt);
876+
assertThat(decodedJWT, is(notNullValue()));
877+
}
878+
879+
@Test
880+
public void shouldVerifyDoubleClaimPresence() {
881+
String jwt = JWTCreator.init()
882+
.withClaim("custom", 12.34)
883+
.sign(Algorithm.HMAC256("secret"));
884+
885+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
886+
.withClaimPresence("custom")
887+
.build();
888+
889+
DecodedJWT decodedJWT = verifier.verify(jwt);
890+
assertThat(decodedJWT, is(notNullValue()));
891+
}
892+
893+
@Test
894+
public void shouldVerifyListClaimPresence() {
895+
String jwt = JWTCreator.init()
896+
.withClaim("custom", Collections.singletonList("item"))
897+
.sign(Algorithm.HMAC256("secret"));
898+
899+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
900+
.withClaimPresence("custom")
901+
.build();
902+
903+
DecodedJWT decodedJWT = verifier.verify(jwt);
904+
assertThat(decodedJWT, is(notNullValue()));
905+
}
906+
907+
@Test
908+
public void shouldVerifyMapClaimPresence() {
909+
String jwt = JWTCreator.init()
910+
.withClaim("custom", Collections.singletonMap("key", "value"))
911+
.sign(Algorithm.HMAC256("secret"));
912+
913+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
914+
.withClaimPresence("custom")
915+
.build();
916+
917+
DecodedJWT decodedJWT = verifier.verify(jwt);
918+
assertThat(decodedJWT, is(notNullValue()));
919+
}
920+
921+
@Test
922+
public void shouldVerifyStandardClaimPresence() {
923+
String jwt = JWTCreator.init()
924+
.withClaim("aud", "any value")
925+
.sign(Algorithm.HMAC256("secret"));
926+
927+
JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
928+
.withClaimPresence("aud")
929+
.build();
930+
931+
DecodedJWT decodedJWT = verifier.verify(jwt);
932+
assertThat(decodedJWT, is(notNullValue()));
933+
}
793934
}

0 commit comments

Comments
 (0)