What is JWT and how does it work?
JWT (JSON Web Token) is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. It's commonly used for authentication and information exchange.
JWT Structure
A JWT consists of three parts separated by dots (.):
``` xxxxx.yyyyy.zzzzz ```
1. Header
Contains token type and signing algorithm:
```json { "alg": "HS256", "typ": "JWT" } ```
2. Payload
Contains claims (user data):
```json { "sub": "1234567890", "name": "John Doe", "email": "john@example.com", "iat": 1516239022, "exp": 1516242622 } ```
3. Signature
Ensures token wasn't tampered with:
```javascript HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret ) ```
Complete Example
``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ```
How Authentication Works
1. User Login
```javascript // Backend (Node.js/Express) app.post('/api/login', async (req, res) => { const { email, password } = req.body;
// Verify credentials const user = await User.findByEmail(email); if (!user || !await user.checkPassword(password)) { return res.status(401).json({ error: 'Invalid credentials' }); }
// Generate JWT const jwt = require('jsonwebtoken'); const token = jwt.sign( { userId: user.id, email: user.email, role: user.role }, process.env.JWT_SECRET, { expiresIn: '24h' } );
res.json({ token }); }); ```
2. Client Stores Token
```javascript // Frontend const login = async (email, password) => { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) });
const { token } = await response.json();
// Store in localStorage localStorage.setItem('token', token); // Or in memory for better security // sessionStorage.setItem('token', token); }; ```
3. Client Sends Token
```javascript // Include token in requests const getData = async () => { const token = localStorage.getItem('token');
const response = await fetch('/api/protected', { headers: { 'Authorization': `Bearer ${token}` } });
return response.json(); }; ```
4. Server Verifies Token
```javascript // Middleware to verify JWT const jwt = require('jsonwebtoken');
const verifyToken = (req, res, next) => { const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); }
const token = authHeader.substring(7);
try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).json({ error: 'Invalid token' }); } };
// Protected route app.get('/api/protected', verifyToken, (req, res) => { res.json({ data: 'Secret data', user: req.user }); }); ```
JWT Claims
Standard Claims (Registered)
- `iss` (issuer): Token issuer
- `sub` (subject): User ID
- `aud` (audience): Intended recipient
- `exp` (expiration): Expiration time
- `iat` (issued at): Time token was created
- `nbf` (not before): Token not valid before this time
```javascript const token = jwt.sign( { iss: 'myapp.com', sub: '123', aud: 'myapp-api', exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 hour iat: Math.floor(Date.now() / 1000) }, secret ); ```
Best Practices
1. Short Expiration Times
```javascript // Access token: short-lived const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
// Refresh token: long-lived, more secure const refreshToken = jwt.sign(payload, refreshSecret, { expiresIn: '7d' }); ```
2. Secure Storage
```javascript // ❌ Bad: localStorage vulnerable to XSS localStorage.setItem('token', token);
// ✅ Better: httpOnly cookie res.cookie('token', token, { httpOnly: true, secure: true, sameSite: 'strict' });
// ✅ Best: Short-lived tokens + refresh mechanism ```
3. Token Refresh Pattern
```javascript // Frontend let accessToken = ''; let refreshToken = '';
const refreshAccessToken = async () => { const response = await fetch('/api/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) });
const { accessToken: newToken } = await response.json(); accessToken = newToken; };
// Axios interceptor for automatic refresh axios.interceptors.response.use( response => response, async error => { if (error.response?.status === 401) { await refreshAccessToken(); // Retry original request return axios(error.config); } return Promise.reject(error); } ); ```
Common Interview Questions
-
Q: JWT vs Session-based auth? A: JWT is stateless (no server storage), scales better. Sessions store state on server, easier to revoke. JWT is better for microservices/APIs.
-
Q: Can you decrypt JWT? A: JWT is SIGNED, not encrypted. Anyone can decode and read payload. For sensitive data, use JWE (JSON Web Encryption).
-
Q: How to invalidate JWT? A: JWTs can't be invalidated (stateless). Solutions: short expiration, blacklist (defeats stateless purpose), or use refresh tokens.
-
Q: Where to store JWT? A: HttpOnly cookies (best), memory (resets on refresh), localStorage (vulnerable to XSS). Never store in localStorage for sensitive apps.
Security Considerations
✅ DO:
- Use strong secrets (256+ bits)
- Set short expiration times
- Use HTTPS only
- Validate tokens on every request
- Implement refresh token rotation
❌ DON'T:
- Store sensitive data in payload
- Use weak secrets
- Skip expiration validation
- Trust client-side decoding
- Store in localStorage for sensitive data
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.