Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"exp" spec claim - String parsing fallback does not allow UTC offsets #534

Open
aamarfii opened this issue Nov 27, 2019 · 6 comments
Open
Labels
rfc-compliance Required for RFC/spec compliance
Milestone

Comments

@aamarfii
Copy link

"exp" claim should be number, however jjwt supports parsing the string as a number, and as last fallback parsing string as a ISO_8601 formatted date.

In io.jsonwebtoken.lang.DateFormats
there are following patterns

private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";

private static final String ISO_8601_MILLIS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

So they are named here as ISO_8601 and if can't parse the exception says it is not ISO_8601 formatted.

private static Date parseIso8601Date(String s, String name) throws IllegalArgumentException {
    try {
        return DateFormats.parseIso8601Date(s);
    } catch (ParseException e) {
        String msg = "'" + name + "' value does not appear to be ISO-8601-formatted: " + s;
        throw new IllegalArgumentException(msg, e);
    }
}

Despite that ISO_8601 allows you to have the offset

The offset from UTC is appended to the time in the same way that 'Z' was above, in the form ± 
 [hh]:[mm], ±[hh][mm], or ±[hh]

https://en.wikipedia.org/wiki/ISO_8601

The patterns from DateFormats will accept only 'Z' value.

Therefore values like 2019-11-26T15:54:13.100-0800 are not accepted, resulting in ParseException->IllegalArgumentException.

Another point is that it throws IllegalArgumentException, which does not inherit from JwtException, not intuitively this may result in non-caught exception.


In my understanding the fix would be to change the patterns to following (removing the single quotes around Z letter) :

private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";

private static final String ISO_8601_MILLIS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

and throwing MalformedSpecificationClaimException extends JwtException instead of IllegalArgumentException.

@aamarfii
Copy link
Author

4.1.4. “exp” (Expiration Time) Claim
exp value MUST be a number containing a NumericDate value.

  1. “NumericDate”
    NumericDate - A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds.

Allowing anything other than NumericDate is not spec compliant.

@jiangyongbing24
Copy link

4.1.4. “exp” (Expiration Time) Claim
exp value MUST be a number containing a NumericDate value.

  1. “NumericDate”
    NumericDate - A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds.

Allowing anything other than NumericDate is not spec compliant.

I use the following code to test

         Map<String, Object> headerMap = new LinkedHashMap<>();
        headerMap.put("alg","HS512");
        headerMap.put("typ","JWT");
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("accout","admin");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(calendar.MINUTE, 15);
        long exp = calendar.getTimeInMillis();
        System.out.println("JWT creation time is " + exp);
        payload.put("exp", exp);

        String compactJws = Jwts.builder().setHeader(headerMap)
                .setPayload(JSONObject.toJSONString(payload))
                .signWith(JWTConstant.KEY)
                .compact();

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(JWTConstant.KEY).parseClaimsJws(compactJws);
        Claims body = claimsJws.getBody();

        Date expDate = body.getExpiration();
        System.out.println(expDate.getTime());

Why does 'exp' increase a thousand times?

@aamarfii
Copy link
Author

aamarfii commented Dec 16, 2019

@jiangyongbing24 because java.util.Date works with miliseconds, whereas the JWT is epoch-second based, so whatever you pass it is expected to be second, and the library multiplies it by 1000 to follow the java convention.

I recommend you to use setExpiration method which takes the java.util.Date
Jwts.builder().setExpiration(new Date(Instant.now().toEpochMilli()))

@lhazlewood
Copy link
Contributor

@jiangyongbing24 your own quote gives you the answer:

A JSON numeric value representing the number of seconds

not milliseconds.

https://stackoverflow.com/questions/39926104/what-format-is-the-exp-expiration-time-claim-in-a-jwt

@lhazlewood lhazlewood added the rfc-compliance Required for RFC/spec compliance label Dec 16, 2019
@lhazlewood lhazlewood added this to the 1.0 milestone Dec 16, 2019
@lhazlewood
Copy link
Contributor

lhazlewood commented Dec 16, 2019

Adding this to the 1.0 milestone because it is not a backwards-compatible change (and also marked as jwt-compliance). For 1.0, we should remove ISO-8601 support entirely for this exp, nbf and iat claims guarantee spec-compliant usage. If a user wants ISO-8601, they should use their own claim(s) different from these three. Thanks for the issue @aamarfii .

@aamarfii
Copy link
Author

Adding this to the 1.0 milestone because it is not a backwards-compatible change (and also marked as jwt-compliance). For 1.0, we should remove ISO-8601 support entirely for this one claim to guarantee spec-compliant usage. If a user wants ISO-8601, they should use their own claim different from exp. Thanks for the issue @aamarfii .

@lhazlewood same applies for nbf and iat spec claims

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfc-compliance Required for RFC/spec compliance
Projects
None yet
Development

No branches or pull requests

3 participants