From 39898b365f1a49914270160089988b623d6f6ac2 Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Thu, 6 Aug 2026 12:39:20 +0530 Subject: [PATCH] feat: Added expiresIn support for the token --- EXAMPLES.md | 6 ++++ README.md | 1 + .../main/java/com/auth0/android/jwt/JWT.java | 21 ++++++++++++ .../java/com/auth0/android/jwt/JWTTest.java | 32 +++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 4a7e61f..2ff86bf 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -84,6 +84,12 @@ The JWT token may include DateNumber fields that can be used to validate that th boolean isExpired = jwt.isExpired(10); // 10 seconds leeway ``` +You can also check whether the token will expire within a given interval from now. This is useful when the app wants to proactively refresh the token before it expires. If the `"exp"` claim is missing, this returns `false`. + +```java +boolean expiringSoon = jwt.expiresIn(60); // true if the token expires within the next 60 seconds +``` + ## Private Claims diff --git a/README.md b/README.md index 84602d8..a72cb67 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ JWT jwt = new JWT(token); String issuer = jwt.getIssuer(); //get registered claims String claim = jwt.getClaim("isAdmin").asString(); //get custom claims boolean isExpired = jwt.isExpired(10); // Do time validation with 10 seconds leeway +boolean expiringSoon = jwt.expiresIn(60); // true if the token expires within the next 60 seconds ``` A `DecodeException` will raise with a detailed message if the token has: diff --git a/lib/src/main/java/com/auth0/android/jwt/JWT.java b/lib/src/main/java/com/auth0/android/jwt/JWT.java index 7c52d8b..fd163ee 100644 --- a/lib/src/main/java/com/auth0/android/jwt/JWT.java +++ b/lib/src/main/java/com/auth0/android/jwt/JWT.java @@ -170,6 +170,27 @@ public boolean isExpired(long leeway) { return !expValid || !iatValid; } + /** + * Validates whether this JWT will expire within the given number of seconds from now. + * Useful when the app wants to refresh the token before its expiration time. + * + * @param seconds the interval in seconds from now to check the expiration against. Must be a positive value. + * @return true if the token expires within the given interval, or false otherwise. If the "exp" claim is missing, returns false. + */ + public boolean expiresIn(long seconds) { + if (seconds < 0) { + throw new IllegalArgumentException("The seconds must be a positive value."); + } + + if (payload.exp == null) { + return false; + } + + long todayTime = (long) (Math.floor(new Date().getTime() / 1000) * 1000); + Date futureDate = new Date(todayTime + seconds * 1000); + return !futureDate.before(payload.exp); + } + /** * Returns the String representation of this JWT. * diff --git a/lib/src/test/java/com/auth0/android/jwt/JWTTest.java b/lib/src/test/java/com/auth0/android/jwt/JWTTest.java index 14318c7..ec4b437 100644 --- a/lib/src/test/java/com/auth0/android/jwt/JWTTest.java +++ b/lib/src/test/java/com/auth0/android/jwt/JWTTest.java @@ -313,6 +313,38 @@ public void shouldThrowIfLeewayIsNegative() { jwt.isExpired(-1); } + @Test + public void shouldNotExpireInIntervalWhenExpClaimIsMissing() { + JWT jwt = customTimeJWT(null, null); + assertThat(jwt.expiresIn(60), is(false)); + } + + @Test + public void shouldNotExpireInIntervalWhenExpiryIsBeyondInterval() { + JWT jwt = customTimeJWT(null, new Date().getTime() + 5000); + assertThat(jwt.expiresIn(2), is(false)); + } + + @Test + public void shouldExpireInIntervalWhenExpiryIsWithinInterval() { + JWT jwt = customTimeJWT(null, new Date().getTime() + 2000); + assertThat(jwt.expiresIn(5), is(true)); + } + + @Test + public void shouldExpireInIntervalWhenAlreadyExpired() { + JWT jwt = customTimeJWT(null, new Date().getTime() - 2000); + assertThat(jwt.expiresIn(0), is(true)); + } + + @Test + public void shouldThrowIfExpiresInSecondsIsNegative() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("The seconds must be a positive value."); + JWT jwt = customTimeJWT(null, new Date().getTime() + 2000); + jwt.expiresIn(-1); + } + @SuppressWarnings("ConstantConditions") @Test public void shouldNotRemoveKnownPublicClaimsFromTree() {