JSON Web Tokens (JWT) are an open, industry-standard method for representing claims securely between two parties. They are commonly used in modern web applications for stateless authentication and information exchange.
Understand the structure and components of JWTs, including headers, payloads, and signatures.
Learn how to generate, verify, and use JWTs for secure authentication in web applications.
Master JWT security considerations, including token expiration, refresh tokens, and proper storage.
JSON Web Tokens (JWT) are a way to transmit information between parties in the form of a JSON object. The JSON information is most commonly used for authentication and information exchange.
A JWT is a string that has three parts separated by a period (.). Those are:
The header contains the algorithm with the token type. Typically the header for a JWT looks like this:
{ "alg": "HS256", "typ": "JWT" }
The alg
key specifies which algorithm was used to create the token, in this case, the algorithm is HMACSHA-256, and the typ
property classifies this token as being of the type JWT.
The payload includes claims (fancy name for things like permissions for the user) information or any other data we'd like to store in the token, which is most likely a user id. There are specific claims defined in the JWT standard, and you can also add custom properties to this object.
An example payload:
{ "sub": "1234567890", // standard - subject, normally the user id "name": "John Doe", // custom property "iat": 1516239022 // standard - The Date the token was issued, expressed in seconds since epoch. }
sub
(subject): Entity to whom the token belongs, usually a user IDiat
(issued at): Time at which the token was issuedexp
(expiration time): After this time, the token is invalidnbf
(not before): Token can't be used before this timeiss
(issuer): Identifies who issued the tokenTo create a signature, we must create a string by base64 encoding the header and payload together and then signing it with a secret.
The signature is created using the following pseudocode:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
Combining all three parts, you will get a JWT that looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
You can visit jwt.io and use the debugger to see an excellent representation of a JWT.
When implementing JWT-based authentication, we need to generate tokens during login and verify them on subsequent requests.
const jwt = require('jsonwebtoken'); // Secret key for signing tokens (store in env variables in production) const JWT_SECRET = 'your_jwt_secret'; // Function to generate a token function generateToken(user) { const payload = { subject: user.id, // standard claim for user id username: user.username, // custom data role: user.role, // custom data for authorization iat: Date.now() / 1000 // issued at time in seconds }; const options = { expiresIn: '1h' // token expires in 1 hour }; return jwt.sign(payload, JWT_SECRET, options); }
Once we have the token generation function, we can implement a login endpoint:
// Login endpoint app.post('/api/login', async (req, res) => { const { username, password } = req.body; try { // Verify credentials (pseudo code) const user = await getUserByUsername(username); // Check if user exists and password matches if (!user || !await bcrypt.compare(password, user.password)) { return res.status(401).json({ message: 'Invalid credentials' }); } // Generate token const token = generateToken(user); // Send the token in the response res.status(200).json({ message: 'Login successful', token }); } catch (error) { res.status(500).json({ message: 'Server error during login' }); } });
To protect routes and require authentication, we need middleware to verify the JWT:
// JWT authentication middleware function authenticateToken(req, res, next) { // Get token from Authorization header const authHeader = req.headers.authorization; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN if (!token) { return res.status(401).json({ message: 'Access denied. No token provided.' }); } try { // Verify the token const decoded = jwt.verify(token, JWT_SECRET); // Add the decoded token to the request object req.user = decoded; // Continue to the next middleware/route handler next(); } catch (error) { return res.status(403).json({ message: 'Invalid token.' }); } } // Example of protected route using JWT app.get('/api/protected', authenticateToken, (req, res) => { res.json({ message: 'This is protected data', user: req.user }); });
When using JWTs, we need to consider several security aspects:
Always set a reasonable expiration time on tokens to limit the damage if a token is compromised. Use the expiresIn
option when signing tokens.
Store JWT secrets in environment variables, never hardcode them into your application.
Always use HTTPS in production to protect tokens in transit. JWTs sent over HTTP are vulnerable to interception.
Keep the payload small. Don't include sensitive data in it as the payload can be easily decoded (though not tampered with due to signature verification).
For improved security, use a two-token system: short-lived access tokens and longer-lived refresh tokens.
// Generate both access and refresh tokens function generateTokens(user) { const accessToken = jwt.sign( { subject: user.id, username: user.username }, JWT_ACCESS_SECRET, { expiresIn: '15m' } // short-lived ); const refreshToken = jwt.sign( { subject: user.id }, JWT_REFRESH_SECRET, { expiresIn: '7d' } // longer-lived ); return { accessToken, refreshToken }; } // Login route providing both tokens app.post('/api/login', async (req, res) => { // ... verify credentials const tokens = generateTokens(user); // Store refresh token in DB (important for token revocation) await storeRefreshToken(user.id, tokens.refreshToken); res.json(tokens); }); // Refresh token endpoint app.post('/api/refresh-token', async (req, res) => { const { refreshToken } = req.body; if (!refreshToken) { return res.status(401).json({ message: 'Refresh token required' }); } try { // Verify the refresh token const decoded = jwt.verify(refreshToken, JWT_REFRESH_SECRET); // Check if token is in our database and not revoked const storedToken = await getRefreshToken(decoded.subject, refreshToken); if (!storedToken) { return res.status(403).json({ message: 'Invalid refresh token' }); } // Get user and generate new access token const user = await getUserById(decoded.subject); const accessToken = jwt.sign( { subject: user.id, username: user.username }, JWT_ACCESS_SECRET, { expiresIn: '15m' } ); res.json({ accessToken }); } catch (error) { res.status(403).json({ message: 'Invalid refresh token' }); } });
Proper client-side handling of JWTs is crucial for security:
There are several options for storing JWTs in the browser:
// Example React hook for JWT authentication function useAuth() { const [token, setToken] = useState(null); const [user, setUser] = useState(null); // Login function const login = async (username, password) => { try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (!response.ok) throw new Error('Login failed'); const data = await response.json(); // Store token in memory (not localStorage for security) setToken(data.token); // Decode user info from token const decoded = parseJwt(data.token); setUser(decoded); return true; } catch (error) { console.error('Login error:', error); return false; } }; // Function to add token to requests const authFetch = async (url, options = {}) => { if (!token) throw new Error('No authentication token'); const headers = { ...options.headers, 'Authorization': `Bearer ${token}` }; return fetch(url, { ...options, headers }); }; return { user, login, authFetch }; }
Build a secure authentication system using JWT with Node.js and Express
View Project