From 181dc51c5119c786d478d5b960d23ea59e0c9d1f Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Fri, 5 Jun 2026 15:45:04 +0900 Subject: [PATCH] Report out-of-range NumericDate claims distinctly from non-numeric ones exp, nbf and iat are NumericDate claims, which per RFC 7519 are JSON numbers and may be written in scientific notation. getInstantFromSeconds gated on canConvertToLong() and threw "contained a non-numeric date value" when it was false. For a numeric value that overflows a long (for example 1.733162101e+26 from issue #706) that message is wrong: the value is numeric, just not representable as a NumericDate. Split the two failure modes so a genuinely non-numeric value keeps the existing message, while a numeric value that does not fit a long throws a message naming the value and the real reason. In-range scientific values such as 1.7e9 continue to decode to the correct instant. Fixes #706 Signed-off-by: Arpit Jain --- .../auth0/jwt/impl/PayloadDeserializer.java | 12 +++++++- .../jwt/impl/PayloadDeserializerTest.java | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java index b1d32a12..69df87a1 100644 --- a/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java +++ b/lib/src/main/java/com/auth0/jwt/impl/PayloadDeserializer.java @@ -74,10 +74,20 @@ Instant getInstantFromSeconds(Map tree, String claimName) { if (node == null || node.isNull()) { return null; } - if (!node.canConvertToLong()) { + // A NumericDate per RFC 7519 is a JSON number and may be written in scientific notation + // (for example 1.7e9). Split the two failure modes so the thrown error is accurate: a + // genuinely non-numeric value, versus a numeric value that is out of the range a long can + // hold (such as 1.733162101e+26). Previously both produced "non-numeric date value", which + // is wrong for the second case and made large/scientific values look unsupported. + if (!node.isNumber()) { throw new JWTDecodeException( String.format("The claim '%s' contained a non-numeric date value.", claimName)); } + if (!node.canConvertToLong()) { + throw new JWTDecodeException(String.format( + "The claim '%s' value (%s) is out of the range representable as a NumericDate.", + claimName, node.asText())); + } return Instant.ofEpochSecond(node.asLong()); } diff --git a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java index c3e04013..f734b572 100644 --- a/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java +++ b/lib/src/test/java/com/auth0/jwt/impl/PayloadDeserializerTest.java @@ -241,6 +241,34 @@ public void shouldGetLargeInstantWhenParsingNumericNode() { assertThat(instant.toEpochMilli(), is(2147493647L * 1000)); } + // https://github.com/auth0/java-jwt/issues/706 - a NumericDate may be written in scientific + // notation. An in-range value must still resolve to the right instant. + @Test + public void shouldGetInstantWhenParsingScientificNotationNode() { + Map tree = new HashMap<>(); + DoubleNode node = new DoubleNode(1.7e9); + tree.put("key", node); + + Instant instant = deserializer.getInstantFromSeconds(tree, "key"); + assertThat(instant, is(notNullValue())); + assertThat(instant, is(Instant.ofEpochSecond(1_700_000_000L))); + } + + // A numeric value that does not fit a long (the exact value reported in issue #706) must be + // rejected as out of range, not mislabeled as non-numeric. + @Test + public void shouldThrowOutOfRangeWhenNumericDateExceedsLong() { + exception.expect(JWTDecodeException.class); + exception.expectMessage( + "The claim 'key' value (1.733162101E26) is out of the range representable as a NumericDate."); + + Map tree = new HashMap<>(); + DoubleNode node = new DoubleNode(1.733162101e+26); + tree.put("key", node); + + deserializer.getInstantFromSeconds(tree, "key"); + } + @Test public void shouldGetNullStringWhenParsingNullNode() { Map tree = new HashMap<>();