Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expiration of JWT not working when using expiration date in UTC

Tags:

java

date

utc

jwt

jjwt

I am using jjwt for jwt token creation. Everything works fine when setting expiration date with local system time, i.e.

Date expDate = new Date(new Date().getTime() + 180000); //java.util.Date

But I tried using UTC format date time and signed the jwt token with same 3 min expiry date. And now it is throwing ExpiredJwtException though even i am validating as soon as creating the token. I am using SimpleDateFormat for setting timezone to utc. This is my code for creating token using jjwt in java:

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date expDate, issDate;
    try {
        expDate = (Date) simpleDateFormat.parse(sdf.format(new Date().getTime() + 180000));
        issDate = (Date) simpleDateFormat.parse(sdf.format(new Date().getTime()));
        JwtBuilder builder = Jwts.builder()
                .setExpiration(expDate)
                .setIssuedAt(issDate)
                .setId(id)
                .signWith(signingKey, signatureAlgorithm);
        jwtToken = builder.compact();
    } catch (ParseException ex) {
    }

The token gets successfully created. I can verify the contents online as well. expDate is 3 min ahead of issDate. I am also calling method for verifying the token as soon as after it was created by passing that created token. My verification method has:

    try {
        Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token);
        log.info("jwt verification success");
    } catch (ExpiredJwtException exJwt) {
        log.info("expired jwt : \n{}", exJwt.getMessage());
    } catch (JwtException e) {
        log.info("tampered jwt");
    }

But I am getting ExpiredJwtException. The error is

expired jwt : JWT expired at 2019-05-17T01:24:48Z. Current time: 2019-05-17T07:06:48Z, a difference of 20520836 milliseconds. Allowed clock skew: 0 milliseconds.

From my log, the issued date and expiration date in my token at this time is:

issued date is: 2019-05-17T07:06:48.000+0545
expiry date is: 2019-05-17T07:09:48.000+0545

How is this happening? And thank you for you help.

like image 989
Ujjwal Jung Thapa Avatar asked May 17 '19 07:05

Ujjwal Jung Thapa


People also ask

How do I fix a JWT token that has expired?

So in summary when authorization is successful you need to issue two token ACCESS_TOKEN and REFRESH_TOKEN. When ACCESS_TOKEN expires you need to call another api with REFRESH_TOKEN to get new ACCESS_TOKEN. The client application can get a new access token as long as the refresh token is valid and unexpired.

What is the default JWT token expiration time?

Access token expiration is set to 24 hours by default.

How does JWT expiration work?

The API returns a short-lived token (JWT), which expires in 15 minutes, and in HTTP cookies, the refresh token expires in 7 days. JWT is currently used for accessing secure ways on API, whereas a refresh token generates another new JWT access token when it expires or even before.


1 Answers

There's no need for SimpleDateFormat here, as Date represents the number of milliseconds since the Unix Epoch, that is, midnight on January 1st 1970 (UTC).

What may cause confusion, however, is the toString() method, as it applies the JVM's default time zone when generating a string representing that value.

As you are concerned about UTC, let me just bring your attention to what the Coordinated Universal Time (UTC) actually is: It is a time standard (and not a timezone) and it's determined by highly precise atomic clocks combined with the Earth's rotation.

The UTC time standard was adjusted several times until 1972, when leap seconds were introduced to keep UTC in line with the Earth's rotation, which is not entirely even, and less exact than atomic clocks. As the Earth’s rotation is slowing down, every now and then we have to insert leap seconds here and there:

A graph from xkcd documenting the war between timekeepers and time

While the internal value of Date is intended to reflect UTC, it may not do so exactly due to those leap seconds.

Java 8 and the new API for date and time

Even though Date suits your needs when it comes to UTC, you should avoid it. It's a legacy class now.

Java 8 introduced a new API for dates, times, instants and durations based on the ISO calendar system. The closest equivalent to Date is Instant which represents a timestamp, a moment on the timeline in UTC.

To capture the current moment in UTC, you can use the following:

Instant.now();              // Capture the current moment in UTC

And you can use the following to get a string representing such value:

Instant.now().toString();   // 2019-05-17T12:50:40.474Z

This string is formatted according the ISO 8601, where Z indicates that the given time is in UTC.

Interoperability with JJWT

For interoperability with JJWT, which doesn't support the java.time types yet, you can create an instance of Date from Instant:

Date.from(Instant.now());   // Convert from modern class to legacy class

And here's a test that demonstrates how you can issue and validate a token:

@Test
public void shouldMatchIssuedAtAndExpiration() {

    Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

    Instant issuedAt = Instant.now().truncatedTo(ChronoUnit.SECONDS);
    Instant expiration = issuedAt.plus(3, ChronoUnit.MINUTES);

    log.info("Issued at: {}", issuedAt);
    log.info("Expires at: {}", expiration);

    String jws = Jwts.builder()
            .setIssuedAt(Date.from(issuedAt))
            .setExpiration(Date.from(expiration))
            .signWith(key)
            .compact();

    Claims claims = Jwts.parser()
            .setSigningKey(key)
            .parseClaimsJws(jws)
            .getBody();

    assertThat(claims.getIssuedAt().toInstant(), is(issuedAt));
    assertThat(claims.getExpiration().toInstant(), is(expiration));
}

For the above example, I've used JJWT 0.10.5 with the dependencies listed in the documentation. In case you need, the above code was written with the following import statements:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.security.Key;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.Date;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
like image 111
cassiomolin Avatar answered Oct 10 '22 02:10

cassiomolin