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.
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.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.