JSON Web Tokens (JWT) provide a simple, scalable way to authenticate users in your APIs without storing session state on the server. A JSON Web Token is a compact string that represents signed JSON data. It consists of three parts, each Base64-URL encoded and separated by dots:
<Header>.<Payload>.<Signature>
- Header – Specifies the signing algorithm and token type.
- Payload – Contains claims (e.g. user ID, roles, issued-at/expiration timestamps).
- Signature – Ensures the token hasn’t been tampered with.
Because the signature is derived from the header + payload and a secret (or private key), you can verify both the integrity and authenticity of the token.
How JWTs Work
- Client logs in with credentials (e.g. email/password).
- Server validates the credentials and issues a JWT (jwt.sign).
- Client stores the token (e.g. in memory, localStorage, or a secure cookie).
- Subsequent requests include the token in the Authorization header (Bearer <token>).
- Server verifies the token (jwt.verify) on each request—no session lookup required.
Install & Configure
In most of my Node.js applications that use JWT for authentication, I use a popular library called jsonwebtoken. This can be installed with the below command.
npm install jsonwebtokenUse the --save flag with the above command to add it to your project’s package.json file.
Generating a Token
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET || 'replace-with-strong-secret';
// Create a payload with standard and custom claims
const payload = {
sub: user.id, // subject (user identifier)
name: user.name, // custom claim
role: user.role // custom claim
};
// Sign a token that expires in 1 hour
const token = jwt.sign(payload, SECRET, { expiresIn: '1h' });
console.log('Generated JWT:', token);The above code shows how to create a token and sign it. You can add whatever data you need to authorize the user in the payload section of the token.
Verifying a Token
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
try {
const decoded = jwt.verify(token, SECRET);
console.log('Decoded payload:', decoded);
} catch (err) {
// TokenExpiredError or JsonWebTokenError
console.error('JWT verification failed:', err.message);
}In the above code, you can see how to validate a token, and then retrieve/decode the information that you stored in its payload.
Protecting Routes with Express Middleware
// auth.js
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader?.split(' ')[1]; // Bearer <token>
if (!token) return res.sendStatus(401); // Unauthorized
jwt.verify(token, SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Forbidden
req.user = user; // attach payload to req
next();
});
}
module.exports = authenticateToken;The above code shows how to create a generic function that you can use to authenticate a token.
// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const authenticateToken = require('./auth');
const app = express();
app.use(express.json());
// Public login route
app.post('/login', (req, res) => {
// TODO: validate user credentials…
const token = jwt.sign({ sub: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});
// Protected profile route
app.get('/profile', authenticateToken, (req, res) => {
res.json({ message: `Welcome back, ${req.user.name}!` });
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));The above code shows how to add a middleware function to a route. If the function validates the token, the route can be executed. Otherwise, the user gets a 403 error.
Best Practices
Here are some best practices for using JSON Web Tokens (JWT) for authentication in a Node.js application. First of all, use short expiration values. In my example above, my token expires in 1 hour, but you can specify a value in minutes (15 minutes, for example). If you need your users to have longer sessions, a recommended practice is to implement refresh tokens.
Second, and this should go without saying, but never save sensitive information in the payload of the token. Data like passwords or personal information. And, always use HTTPS in your applications (whether you use JWT or not).
And, finally, the SECRET for signing and verifying tokens should be saved in a secure.
Conclusion
With JWTs and the jsonwebtoken package, you can build a stateless, scalable authentication layer for your Node.js applications. This approach reduces server memory overhead and simplifies horizontal scaling—perfect for APIs, SPAs, and mobile backends.
Ready to take it further? Consider adding refresh-token rotation, role-based access control, or integrating with OAuth2 providers.
Happy coding!
