Understanding how JWT works
json Web Tokens (JWT) are a compact, url-safe way to represent claims between two parties. A token consists of three parts encoded in Base64URL: a header that indicates the signing algorithm, a payload with claims (for example user id, roles, and expiration), and a signature that proves the token was issued by a trusted party. Because the token contains its own data and a signature, it lets servers verify authenticity without keeping a session store for every client when using stateless authentication patterns. That said, careful choices about signing algorithms, key management, expiration, and where tokens are stored on the client make the difference between secure and vulnerable deployments.
Choose the right algorithm and keys first
The two common patterns are symmetric (HS256) and asymmetric (RS256 or ES256). HS256 uses a shared secret on both signer and verifier; it’s straightforward and good for systems where a single backend signs and validates tokens. RS256 uses a private key to sign and a public key to verify; this is preferable when different services or third parties need to verify tokens without access to the private key. For production, prefer asymmetric signing if you need to rotate keys or distribute verification to many services. Regardless of algorithm, keep private keys and secrets out of code, rotate them periodically, and use environment variables or a secrets manager to store them.
Step-by-step JWT configuration
1. Install a JWT library
Pick a well-maintained library for your platform. In Node.js the popular choice is jsonwebtoken; in Python use PyJWT; in Java use Nimbus JOSE+JWT or jjwt. Install and import the library so you can sign and verify tokens using idiomatic code for your language.
// Node.js installation
npm install jsonwebtoken
// Basic import
const jwt = require('jsonwebtoken');
2. Generate signing keys or secrets
For HS256 you need a long, random secret string stored securely. For RS256 generate an RSA key pair and store the private key privately while sharing the public key with verifiers. Example OpenSSL commands create keys you can load into your application.
# Generate RSA private key
openssl genpkey -algorithm RSA -out jwt_private.pem -pkeyopt rsa_keygen_bits:2048
# Extract public key
openssl rsa -pubout -in jwt_private.pem -out jwt_public.pem
3. Create and sign tokens
Build a payload with the claims you need: a unique subject (sub), issued timestamp (iat), and expiration (exp) are common minimums. Keep sensitive data out of the payload because clients can decode it. Sign the payload with the chosen algorithm and key, and set a sensible expiration,shorter TTLs for access tokens reduce risk. Example Node.js snippet below uses HS256; an RS256 example follows it.
// HS256 example (symmetric secret)
const payload = { sub: 'userId123', role: 'user' };
const secret = process.env.JWT_SECRET; // store securely
const token = jwt.sign(payload, secret, { algorithm: 'HS256', expiresIn: '1h' });
// RS256 example (asymmetric keys)
const privateKey = fs.readFileSync('./jwt_private.pem', 'utf8');
const tokenRs = jwt.sign(payload, privateKey, { algorithm: 'RS256', expiresIn: '15m' });
4. Verify tokens on incoming requests
Validate the signature, check the expiration and other claims (aud, iss, sub) on every request that requires authentication. Return clear error codes for expired or invalid tokens, and avoid leaking details. If you use RS256, load the public key on the verifier side and keep it separate from the signing key. In Express, put verification in middleware so your routes see only authenticated requests.
// Express middleware example
function verifyToken(req, res, next) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) return res.status(401).send('Missing token');
const token = auth.split(' ')[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET); // or publicKey for RS256
req.user = payload;
next();
} catch (err) {
return res.status(401).send('Invalid or expired token');
}
}
5. Handle expiration and refresh tokens
Use short-lived access tokens and longer-lived refresh tokens. Give access tokens lifetimes measured in minutes to an hour and refresh tokens lifetimes measured in days or weeks. Store refresh tokens securely on the server (or as HttpOnly cookies) and rotate them: when a refresh is used, issue a new refresh token and revoke the old one. This limits the window for stolen refresh tokens and makes revocation practical.
- Access token: short-lived, keeps server stateless.
- Refresh token: long-lived, stored securely, tracked/rotated server-side.
- Rotation: issue a new refresh token on each refresh and invalidate the old one.
6. Store tokens securely on the client
Where tokens live on the client affects attack surface. Avoid storing sensitive tokens in localStorage because JavaScript can read it in the event of an XSS vulnerability. Prefer HttpOnly, Secure cookies with SameSite set to Lax or Strict for web apps, which prevents JavaScript access and mitigates some types of cross-site request forgery (CSRF) when combined with proper CSRF protections. For native apps, use the platform’s secure storage.
7. Implement revocation and rotation strategies
Because JWTs are often stateless, revocation requires planning. Options include maintaining a revocation list or token blacklist, using a “token version” or “last logout” timestamp in the user record that you compare against a token claim, or issuing very short-lived access tokens and relying on refresh token revocation. Choose the strategy that balances performance and control for your environment.
8. Use secure transport and headers
Always send tokens over https to prevent interception. Place them in the Authorization header using the Bearer scheme instead of including them in query parameters or URL fragments. When using cookies, set Secure, HttpOnly, and SameSite attributes and apply proper CSRF mitigations for cookie-based flows.
9. Test, monitor and rotate keys
Include automated tests that handle valid/expired/invalid tokens, simulate stolen tokens, and confirm revocation behaves correctly. Monitor authentication endpoints for unusual activity and set alerts for spikes in invalid token attempts. Establish a key rotation policy and support serving multiple public keys (via a JWKS endpoint) so verifiers can validate tokens signed with older keys during a transition period.
Common mistakes and best practices
Several recurring mistakes weaken JWT security: putting secrets into source control, using very long-lived access tokens, storing tokens in localStorage for web apps, skipping strict validation of claims, and failing to rotate or revoke compromised keys. To avoid these issues, centralize secrets in a secure vault or environment variables, prefer short access token lifetimes, store tokens securely on clients, verify all expected claims (iss, aud, exp), log and audit authentication events, and adopt key rotation with a clear rollback plan.
Summary
Configuring JWT securely means more than signing a payload: pick the right algorithm and key management approach, sign with appropriate claims and expiration, verify tokens on every request, secure storage on the client, implement refresh/rotation and revocation, and test and monitor your authentication flows. With sensible defaults,short-lived access tokens, HttpOnly cookies or secure storage, and clear rotation policies,you can use JWT to build scalable, robust authentication systems without introducing unnecessary risk.
FAQs
1. Should I use HS256 or RS256?
Use HS256 when your signer and verifier are the same trusted service and you prefer simpler key management. Choose RS256 when different services need to verify tokens or when you want to keep the private key isolated; RS256 supports public key distribution without revealing signing credentials.
2. Where should I store access and refresh tokens on the client?
For web applications, prefer HttpOnly, Secure cookies with a SameSite policy for refresh tokens or for full session flows. Avoid localStorage for sensitive tokens because of XSS risks. For single-page apps that must store tokens locally, treat tokens as ephemeral and add strict XSS protections; ideally use a backend-for-frontend pattern with cookies.
3. How should I handle token revocation?
Use one of these patterns: keep short-lived access tokens and revoke refresh tokens server-side, maintain a blacklist or revocation list keyed by token ID, or embed a token version/last-active timestamp in the user record and validate it on each request. Rotation of refresh tokens reduces the window of abuse.
4. Is it OK to put user data inside JWT payload?
You can include non-sensitive user data such as user id, roles, and claims needed for authorization, but never store secrets or private information that should remain confidential. Remember JWT payloads are only base64-encoded, not encrypted,anyone with the token can decode them.
5. How often should I rotate signing keys?
The rotation frequency depends on risk and compliance needs; rotating keys every few months is a reasonable baseline for many applications. Always have a process to publish new public keys (for RS256) via a JWKS endpoint and support multiple active keys during a rollover period to avoid service disruption.



