Skip to content

Commit 7760b2d

Browse files
committed
check time values against TODAY date instead of receiving a date
1 parent 8b676c0 commit 7760b2d

8 files changed

Lines changed: 349 additions & 156 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.auth0.jwt;
2+
3+
import java.util.Date;
4+
5+
/**
6+
* The Clock class is used to wrap calls to Date class.
7+
*/
8+
class Clock {
9+
10+
Clock() {
11+
}
12+
13+
/**
14+
* Returns a new Date representing Today's time.
15+
*
16+
* @return a new Date representing Today's time.
17+
*/
18+
Date getToday() {
19+
return new Date();
20+
}
21+
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ public static JWTVerifier.Verification require(Algorithm algorithm) throws Illeg
3737
return JWTVerifier.init(algorithm);
3838
}
3939

40-
@Override
41-
public boolean isExpired() {
42-
return jwt.isExpired();
43-
}
44-
4540
@Override
4641
public String getSignature() {
4742
return jwt.getSignature();

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

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,4 @@ public String getSignature() {
110110
return signature;
111111
}
112112

113-
@Override
114-
public boolean isExpired() {
115-
final Date iat = getIssuedAt();
116-
final Date nbf = getNotBefore();
117-
final Date exp = getExpiresAt();
118-
final Date today = new Date();
119-
120-
boolean issuedAtValid = iat == null || iat.before(today);
121-
boolean notBeforeValid = nbf == null || nbf.after(today);
122-
boolean expiresAtValid = exp == null || exp.after(today);
123-
124-
return !issuedAtValid || !notBeforeValid || !expiresAtValid;
125-
}
126113
}

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

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
class JWTVerifier {
1313
private final Algorithm algorithm;
1414
final Map<String, Object> claims;
15+
private final Clock clock;
1516

16-
private JWTVerifier(Algorithm algorithm, Map<String, Object> claims) {
17+
private JWTVerifier(Algorithm algorithm, Map<String, Object> claims, Clock clock) {
1718
this.algorithm = algorithm;
1819
this.claims = Collections.unmodifiableMap(claims);
20+
this.clock = clock;
1921
}
2022

2123
/**
@@ -35,6 +37,7 @@ static JWTVerifier.Verification init(Algorithm algorithm) throws IllegalArgument
3537
static class Verification {
3638
private final Algorithm algorithm;
3739
private final Map<String, Object> claims;
40+
private long defaultDelta;
3841

3942
Verification(Algorithm algorithm) throws IllegalArgumentException {
4043
if (algorithm == null) {
@@ -43,6 +46,7 @@ static class Verification {
4346

4447
this.algorithm = algorithm;
4548
this.claims = new HashMap<>();
49+
this.defaultDelta = 0;
4650
}
4751

4852
/**
@@ -76,32 +80,66 @@ public Verification withAudience(String[] audience) {
7680
}
7781

7882
/**
79-
* Require a specific Expires At ("exp") claim.
83+
* Define the default window in milliseconds in which the Not Before, Issued At and Expires At Claims will still be valid.
84+
* Setting a specific delta value on a given Claim will override this value for that Claim.
8085
*
86+
* @param delta the window in milliseconds in which the Not Before, Issued At and Expires At Claims will still be valid.
8187
* @return this same Verification instance.
88+
* @throws IllegalArgumentException if delta is negative.
8289
*/
83-
public Verification withExpiresAt(Date expiresAt) {
84-
requireClaim(PublicClaims.EXPIRES_AT, expiresAt);
90+
public Verification acceptTimeDelta(long delta) throws IllegalArgumentException {
91+
if (delta < 0) {
92+
throw new IllegalArgumentException("Delta value can't be negative.");
93+
}
94+
this.defaultDelta = delta;
95+
return this;
96+
}
97+
98+
/**
99+
* Set a specific delta window in milliseconds in which the Expires At ("exp") Claim will still be valid.
100+
* Expiration Date is always verified when the value is present. This method overrides the value set with acceptTimeDelta
101+
*
102+
* @param delta the window in milliseconds in which the Expires At Claim will still be valid.
103+
* @return this same Verification instance.
104+
* @throws IllegalArgumentException if delta is negative.
105+
*/
106+
public Verification acceptExpiresAt(long delta) throws IllegalArgumentException {
107+
if (delta < 0) {
108+
throw new IllegalArgumentException("Delta value can't be negative.");
109+
}
110+
requireClaim(PublicClaims.EXPIRES_AT, delta);
85111
return this;
86112
}
87113

88114
/**
89-
* Require a specific Not Before ("nbf") claim.
115+
* Set a specific delta window in milliseconds in which the Not Before ("nbf") Claim will still be valid.
116+
* Not Before Date is always verified when the value is present. This method overrides the value set with acceptTimeDelta
90117
*
118+
* @param delta the window in milliseconds in which the Not Before Claim will still be valid.
91119
* @return this same Verification instance.
120+
* @throws IllegalArgumentException if delta is negative.
92121
*/
93-
public Verification withNotBefore(Date notBefore) {
94-
requireClaim(PublicClaims.NOT_BEFORE, notBefore);
122+
public Verification acceptNotBefore(long delta) throws IllegalArgumentException {
123+
if (delta < 0) {
124+
throw new IllegalArgumentException("Delta value can't be negative.");
125+
}
126+
requireClaim(PublicClaims.NOT_BEFORE, delta);
95127
return this;
96128
}
97129

98130
/**
99-
* Require a specific Issued At ("iat") claim.
131+
* Set a specific delta window in milliseconds in which the Issued At ("iat") Claim will still be valid.
132+
* Issued At Date is always verified when the value is present. This method overrides the value set with acceptTimeDelta
100133
*
134+
* @param delta the window in milliseconds in which the Issued At Claim will still be valid.
101135
* @return this same Verification instance.
136+
* @throws IllegalArgumentException if delta is negative.
102137
*/
103-
public Verification withIssuedAt(Date issuedAt) {
104-
requireClaim(PublicClaims.ISSUED_AT, issuedAt);
138+
public Verification acceptIssuedAt(long delta) throws IllegalArgumentException {
139+
if (delta < 0) {
140+
throw new IllegalArgumentException("Delta value can't be negative.");
141+
}
142+
requireClaim(PublicClaims.ISSUED_AT, delta);
105143
return this;
106144
}
107145

@@ -121,7 +159,31 @@ public Verification withJWTId(String jwtId) {
121159
* @return a new JWTVerifier instance.
122160
*/
123161
public JWTVerifier build() {
124-
return new JWTVerifier(algorithm, claims);
162+
return this.build(new Clock());
163+
}
164+
165+
/**
166+
* Creates a new and reusable instance of the JWTVerifier with the configuration already provided.
167+
* ONLY FOR TEST PURPOSES.
168+
*
169+
* @param clock the instance that will handle the current time.
170+
* @return a new JWTVerifier instance with a custom Clock.
171+
*/
172+
JWTVerifier build(Clock clock) {
173+
addDeltaToDateClaims();
174+
return new JWTVerifier(algorithm, claims, clock);
175+
}
176+
177+
private void addDeltaToDateClaims() {
178+
if (!claims.containsKey(PublicClaims.EXPIRES_AT)) {
179+
claims.put(PublicClaims.EXPIRES_AT, defaultDelta);
180+
}
181+
if (!claims.containsKey(PublicClaims.NOT_BEFORE)) {
182+
claims.put(PublicClaims.NOT_BEFORE, defaultDelta);
183+
}
184+
if (!claims.containsKey(PublicClaims.ISSUED_AT)) {
185+
claims.put(PublicClaims.ISSUED_AT, defaultDelta);
186+
}
125187
}
126188

127189
private void requireClaim(String name, Object value) {
@@ -167,19 +229,30 @@ private void verifyClaims(JWT jwt, Map<String, Object> claims) {
167229
}
168230

169231
private void assertValidClaim(JWT jwt, String claimName, Object expectedValue) throws InvalidClaimException {
232+
String errMessage = String.format("The Claim '%s' value doesn't match the required one.", claimName);
170233
boolean isValid;
171234
if (PublicClaims.AUDIENCE.equals(claimName)) {
172235
isValid = Arrays.equals(jwt.getAudience(), (String[]) expectedValue);
173236
} else if (PublicClaims.NOT_BEFORE.equals(claimName) || PublicClaims.EXPIRES_AT.equals(claimName) || PublicClaims.ISSUED_AT.equals(claimName)) {
174-
Date dateValue = (Date) expectedValue;
175-
isValid = dateValue.equals(jwt.getClaim(claimName).asDate());
237+
long deltaValue = (long) expectedValue;
238+
Date today = clock.getToday();
239+
Date date = jwt.getClaim(claimName).asDate();
240+
if (PublicClaims.EXPIRES_AT.equals(claimName)) {
241+
today.setTime(today.getTime() - deltaValue);
242+
isValid = date == null || !today.after(date);
243+
errMessage = String.format("The Token has expired on %s.", date);
244+
} else {
245+
today.setTime(today.getTime() + deltaValue);
246+
isValid = date == null || !today.before(date);
247+
errMessage = String.format("The Token can't be used before %s.", date);
248+
}
176249
} else {
177250
String stringValue = (String) expectedValue;
178251
isValid = stringValue.equals(jwt.getClaim(claimName).asString());
179252
}
180253

181254
if (!isValid) {
182-
throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName));
255+
throw new InvalidClaimException(errMessage);
183256
}
184257
}
185258
}

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,4 @@
44
* The JWT class represents a Json Web Token.
55
*/
66
public interface JWT extends Payload, Header, Signature {
7-
8-
/**
9-
* Tests whether this token's DateTime values IssuedAt, ExpiresAt and NotBefore are time valid.
10-
* If any of them are missing they won't be taken into account. If the token it's expired it shouldn't be used.
11-
*
12-
* @return whether the token should be used or not.
13-
*/
14-
boolean isExpired();
157
}

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

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@
1212
import java.util.Date;
1313

1414
import static com.auth0.jwt.SignUtils.base64Encode;
15-
import static junit.framework.TestCase.assertFalse;
1615
import static org.hamcrest.MatcherAssert.assertThat;
1716
import static org.hamcrest.Matchers.*;
18-
import static org.junit.Assert.assertTrue;
1917

2018
public class JWTDecoderTest {
2119
@Rule
@@ -173,49 +171,6 @@ public void shouldGetType() throws Exception {
173171
assertThat(jwt.getType(), is("JWS"));
174172
}
175173

176-
@Test
177-
public void shouldBeExpired() throws Exception {
178-
long pastSeconds = System.currentTimeMillis() / 1000;
179-
long futureSeconds = (System.currentTimeMillis() + 10000) / 1000;
180-
181-
JWT issuedAndExpiresInTheFuture = customTimeJWT(futureSeconds, futureSeconds, futureSeconds);
182-
assertTrue(issuedAndExpiresInTheFuture.isExpired());
183-
JWT issuedInTheFuture = customTimeJWT(futureSeconds, null, null);
184-
assertTrue(issuedInTheFuture.isExpired());
185-
186-
JWT issuedAndExpiresInThePast = customTimeJWT(pastSeconds, pastSeconds, pastSeconds);
187-
assertTrue(issuedAndExpiresInThePast.isExpired());
188-
JWT expiresInThePast = customTimeJWT(null, pastSeconds, null);
189-
assertTrue(expiresInThePast.isExpired());
190-
191-
JWT issuedInTheFutureExpiresInThePast = customTimeJWT(futureSeconds, pastSeconds, pastSeconds);
192-
assertTrue(issuedInTheFutureExpiresInThePast.isExpired());
193-
194-
JWT notBeforeThePast = customTimeJWT(null, null, pastSeconds);
195-
assertTrue(notBeforeThePast.isExpired());
196-
}
197-
198-
@Test
199-
public void shouldNotBeExpired() throws Exception {
200-
long pastSeconds = System.currentTimeMillis() / 1000;
201-
long futureSeconds = (System.currentTimeMillis() + 10000) / 1000;
202-
203-
JWT missingDates = customTimeJWT(null, null, null);
204-
assertFalse(missingDates.isExpired());
205-
206-
JWT issuedInThePastExpiresInTheFuture = customTimeJWT(pastSeconds, futureSeconds, futureSeconds);
207-
assertFalse(issuedInThePastExpiresInTheFuture.isExpired());
208-
209-
JWT issuedInThePast = customTimeJWT(pastSeconds, null, null);
210-
assertFalse(issuedInThePast.isExpired());
211-
212-
JWT expiresInTheFuture = customTimeJWT(null, futureSeconds, null);
213-
assertFalse(expiresInTheFuture.isExpired());
214-
215-
JWT notBeforeTheFuture = customTimeJWT(null, null, futureSeconds);
216-
assertFalse(notBeforeTheFuture.isExpired());
217-
}
218-
219174
//Private PublicClaims
220175

221176
@Test
@@ -244,35 +199,10 @@ public void shouldGetNullClaimIfClaimValueIsNull() throws Exception {
244199

245200
//Helper Methods
246201

247-
private JWT customTimeJWT(Long iat, Long exp, Long nbf) {
248-
String header = base64Encode("{}");
249-
StringBuilder bodyBuilder = new StringBuilder("{");
250-
if (iat != null) {
251-
bodyBuilder.append("\"iat\":").append(iat.longValue());
252-
}
253-
if (exp != null) {
254-
if (iat != null) {
255-
bodyBuilder.append(",");
256-
}
257-
bodyBuilder.append("\"exp\":").append(exp.longValue());
258-
}
259-
if (nbf != null) {
260-
if (iat != null || exp != null) {
261-
bodyBuilder.append(",");
262-
}
263-
bodyBuilder.append("\"nbf\":").append(nbf.longValue());
264-
}
265-
bodyBuilder.append("}");
266-
String body = base64Encode(bodyBuilder.toString());
267-
String signature = "sign";
268-
return JWTDecoder.decode(String.format("%s.%s.%s", header, body, signature));
269-
}
270-
271202
private JWT customJWT(String jsonHeader, String jsonPayload, String signature) {
272203
String header = base64Encode(jsonHeader);
273204
String body = base64Encode(jsonPayload);
274205
return JWTDecoder.decode(String.format("%s.%s.%s", header, body, signature));
275206
}
276207

277-
278208
}

0 commit comments

Comments
 (0)