The 4 Most Frequent JWT Authentication Failures
TokenExpiredError / jwt expired
HTTP 401 UnauthorizedJsonWebTokenError: invalid signature
HTTP 403 Forbidden / 401NotBeforeError / jwt not active
HTTP 401 UnauthorizedJsonWebTokenError: jwt malformed
HTTP 400 Bad Request1. Fixing Server Clock Skew Discrepancies
In cloud architectures where microservices run in different availability zones or Docker containers, server system clocks can drift by a few seconds. If an authentication service issues a token at 12:00:00 with a 60-second expiration, but an API gateway's clock is running 5 seconds fast (at 12:00:05), the token may appear expired prematurely.
To resolve clock drift, production JWT verification libraries support a clock tolerance (or leeway) setting. Here is how to configure it in Node.js (jsonwebtoken):
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ["HS256"],
clockTolerance: 30, // 30-second leeway buffer
});
} catch (err) {
if (err.name === "TokenExpiredError") {
// Trigger refresh token rotation
}
}
2. Resolving "Invalid Signature" Errors
An invalid signature indicates that the mathematical hash computed by the verifying server does not match the third segment of the JWT. The most common reasons include:
- Symmetric vs. Asymmetric Mismatch: If your authorization server signs tokens using RS256 (RSA private key), your API server must verify using the matching RSA public key (or JWKS URL), not an HMAC secret string.
- Environment Variable Issues: Trailing newlines (
\n) or missing quotation marks around secret keys in.envfiles cause different key strings across development and production containers. - Reverse Proxy Header Mutation: Proxies or CDNs modifying character encoding in the
Authorizationheader can inadvertently alter Base64URL characters.
3. The Silent Refresh Architecture
Instead of forcing users to log in again every time an access token expires, modern web applications implement the Silent Refresh Pattern using an HTTP-only refresh token:
- The user logs in and receives a short-lived Access Token (15 min) and an
HttpOnlyRefresh Token cookie (7 days). - The client uses the access token for API calls.
- When an API endpoint responds with
401 TokenExpiredError, an Axios or Fetch interceptor catches the response and requests a fresh access token from/api/auth/refresh. - The server validates the refresh token, issues a new access token, and retries the original API call seamlessly without disturbing the user.