From 2ab4a47bed8f4a7b39462592c58fcdd96230ed98 Mon Sep 17 00:00:00 2001 From: Hossam Rakha Date: Sun, 4 Oct 2026 01:13:03 +0300 Subject: [PATCH] Fix Date claims before the Unix epoch rounding toward zero Date claims were serialized with truncating division, so any Date before 1970-01-01T00:00:00Z serialized to the wrong (later) second: new Date(-500) became 0 instead of -1, silently corrupting the claim. Instant claims already floor correctly via getEpochSecond(). Use Math.floorDiv so both types agree on the boundary second. Fixes #806 --- .../com/auth0/jwt/impl/ClaimsSerializer.java | 4 ++- .../java/com/auth0/jwt/JWTCreatorTest.java | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java b/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java index b1f8e6d3..45d86174 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/ClaimsSerializer.java @@ -81,6 +81,8 @@ private static long instantToSeconds(Instant instant) { } private static long dateToSeconds(Date date) { - return date.getTime() / 1000; + // Floor (not truncate) so dates before the Unix epoch round toward + // negative infinity, matching Instant#getEpochSecond. (GH-806) + return Math.floorDiv(date.getTime(), 1000L); } } diff --git a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java index 53cd267b..44715cac 100644 --- a/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java +++ b/lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java @@ -313,6 +313,36 @@ public void shouldAddExpiresAtInstant() { assertThat(TokenUtils.splitToken(signed)[1], is("eyJleHAiOjE0Nzc1OTJ9")); } + @Test + public void shouldAddExpiresAtBeforeEpochConsistentlyWithInstant() { + // Dates before the Unix epoch must floor to the previous second, + // matching the Instant serialization (GH-806). + Date date = new Date(-500); + Instant instant = date.toInstant(); + + String signedDate = JWTCreator.init() + .withExpiresAt(date) + .sign(Algorithm.HMAC256("secret")); + String signedInstant = JWTCreator.init() + .withExpiresAt(instant) + .sign(Algorithm.HMAC256("secret")); + + assertThat(signedDate, is(notNullValue())); + assertThat(signedInstant, is(notNullValue())); + assertThat(TokenUtils.splitToken(signedDate)[1], is("eyJleHAiOi0xfQ")); + assertThat(TokenUtils.splitToken(signedDate)[1], is(TokenUtils.splitToken(signedInstant)[1])); + } + + @Test + public void shouldAddNotBeforeBeforeEpoch() { + String signed = JWTCreator.init() + .withNotBefore(new Date(-500)) + .sign(Algorithm.HMAC256("secret")); + + assertThat(signed, is(notNullValue())); + assertThat(TokenUtils.splitToken(signed)[1], is("eyJuYmYiOi0xfQ")); + } + @Test public void shouldAddNotBefore() { String signed = JWTCreator.init()