Information Security 10 min read

How to Seamlessly Refresh JWT Tokens: Backend and Frontend Strategies

This article explains why sudden logouts occur due to expired JWT tokens stored in Redis, and presents both backend automatic token renewal and frontend double‑token (access‑token and refresh‑token) approaches, complete with code examples, testing tips, and handling edge cases such as long‑idle form submissions.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How to Seamlessly Refresh JWT Tokens: Backend and Frontend Strategies

When a user performs operations, the system may suddenly log out and redirect to the login page because the token stored in Redis expires.

Solution: Refresh the token automatically.

Backend token refresh – automatic token renewal

The backend checks a token's expiration time; if it is close to expiring, it generates a new token and places it in the response header. The front‑end intercepts the header, compares the new token with the old one, and replaces the local token.

Example code:

<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.5.1</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.33</version>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.1</version>
</dependency>

Import dependencies

public class JwtUtil {
    // Token validity: 24 hours
    public static final Long JWT_TTL = 60 * 60 * 1000 * 24;
    // Secret key (replace as needed)
    public static final String JWT_KEY = "qx";

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());
        return builder.compact();
    }

    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

Write a unit test

@Test
void test() throws Exception {
    String token = JwtUtil.createJWT("1735209949551763457");
    System.out.println("Token: " + token);
    Date tokenExpirationDate = getTokenExpirationDate(token);
    System.out.println(tokenExpirationDate);
    long exp = tokenExpirationDate.getTime();
    long cur = System.currentTimeMillis();
    System.out.println(exp - cur);
}

public static Date getTokenExpirationDate(String token) {
    try {
        SecretKey secretKey = generalKey();
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
        return claims.getExpiration();
    } catch (ExpiredJwtException | SignatureException e) {
        throw new RuntimeException("Invalid token", e);
    }
}

By parsing the JWT you can obtain the expiration time, compare it with the current time, and decide whether to issue a new token.

Front‑end token refresh – token renewal

The front‑end uses a double‑token scheme (access‑token AT and refresh‑token RT). When AT is about to expire, the client uses RT to request a new AT. RT has a longer lifespan, reducing the risk of token hijacking.

Key points:

AT is sent with every request, so it should have a short expiration to limit exposure.

RT is only sent to the authentication service, allowing a longer expiration for convenience.

This mirrors the security benefits of HTTPS over HTTP.

Questions and considerations

If a user fills a form for a long time without any request and then submits, the backend may return 401 because the token expired. The front‑end must detect this, store the form locally, redirect to the login page, and restore the data after re‑authentication.

Pure backend approach

Handle 401 by prompting the user to log in again, then retrieve the saved form data from local storage and repopulate the form.

Pure front‑end approach

Monitor the refresh‑token expiration, refresh it proactively, and implement a draft‑box feature to save form data locally.

backendfrontendsecurityauthenticationJWTToken Refresh
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.