JSON Web Tokens (JWT) are widely used for implementing authentication and authorization in modern applications. They enable secure, stateless communication between parties by embedding user claims in a compact, URL-safe token. This guide details how to implement JWT authentication in a Node.js application using Express.
Overview of JWT Authentication
JSON Web Tokens (JWTs) are a secure way of transmitting information between parties. They offer the following benefits:
- Compact and URL-Safe: Easy to send through query strings, POST parameters, or HTTP headers.
- Stateless Authentication: Eliminates the need for server-side session storage.
- Integrity Verification: Cryptographic signatures ensure token integrity.
JWTs contain three parts: a header, a payload, and a signature. The payload contains claims like user identification, while the signature ensures the token's authenticity.
Prerequisites for Implementing JWT in Node.js
Before you start, ensure the following prerequisites are met:
prerequisites
- Node.js (v14 or later) installed on your machine.
- Familiarity with JavaScript and REST APIs.
- A code editor like Visual Studio Code.
- Optionally, install the REST Client extension or use Postman for API testing.
Setting Up the Project
Follow these steps to initialize your Node.js application:
steps
Create a new project and initialize it:
bashmkdir jwt-auth && cd jwt-auth npm init -yInstall required dependencies:
bashnpm install express jsonwebtoken dotenv npm install --save-dev nodemonUpdate
package.jsonto add a development script:json{ "scripts": { "start": "node server.js", "dev": "nodemon server.js" } }
Creating a Basic Express Server
Set up a simple Express server and test its functionality.
steps
Create a
server.jsfile:bashtouch server.jsAdd the following code to initialize the server (using CommonJS syntax for compatibility):
javascriptconst express = require('express'); const app = express(); app.use(express.json()); // Basic route app.get('/posts', (req, res) => { res.json([ { username: 'John', title: 'Post 1' }, { username: 'Jane', title: 'Post 2' } ]); }); app.listen(3000, () => console.log("Server running on port 3000"));Start the server:
bashnpm run devVerify your server at
http://localhost:3000/poststo confirm you receive JSON data.
Integrating JWT for Authentication
To protect your API endpoints, integrate JWT to issue tokens and authenticate users.
steps
Update
server.jsto includejsonwebtokenanddotenvrequires and configure dotenv:javascriptconst jwt = require('jsonwebtoken'); const dotenv = require('dotenv'); dotenv.config();Add a
.envfile to store your secret keys:bashecho "ACCESS_TOKEN_SECRET=your_random_access_token_secret_key" > .env echo "REFRESH_TOKEN_SECRET=your_random_refresh_token_secret_key" >> .envImplement a
/loginroute to issue JWT tokens:javascriptapp.post('/login', (req, res) => { const { username } = req.body; const user = { name: username }; const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); res.json({ accessToken }); });Create middleware to verify tokens:
javascriptfunction authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) return res.sendStatus(401); jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => { if (err) return res.sendStatus(403); req.user = user; next(); }); }Protect the
/postsendpoint:javascriptapp.get('/posts', authenticateToken, (req, res) => { const posts = [ { username: 'John', title: 'Post 1' }, { username: 'Jane', title: 'Post 2' } ]; res.json(posts.filter(post => post.username === req.user.name)); });
Adding Refresh Token Functionality
Enhance security further by introducing refresh tokens.
steps
Create an in-memory store for refresh tokens:
javascriptlet refreshTokens = [];Update
/loginto issue both access and refresh tokens:javascriptapp.post('/login', (req, res) => { const { username } = req.body; const user = { name: username }; const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); const refreshToken = jwt.sign(user, process.env.REFRESH_TOKEN_SECRET); refreshTokens.push(refreshToken); res.json({ accessToken, refreshToken }); });Add an endpoint to refresh tokens:
javascriptapp.post('/token', (req, res) => { const { token } = req.body; if (!token || !refreshTokens.includes(token)) return res.sendStatus(403); jwt.verify(token, process.env.REFRESH_TOKEN_SECRET, (err, user) => { if (err) return res.sendStatus(403); const accessToken = jwt.sign({ name: user.name }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); res.json({ accessToken }); }); });Securely manage refresh tokens by adding a
/logoutroute:javascriptapp.delete('/logout', (req, res) => { refreshTokens = refreshTokens.filter(token => token !== req.body.token); res.sendStatus(204); });
Security Best Practices for JWT
Final Steps: Testing and Deployment
Once your JWT-based authentication is set up, test the API endpoints thoroughly using tools like Postman or the REST Client extension. Before deploying, ensure the following:
For further clarity, here are answers to some common questions:
FAQ
How do you store refresh tokens securely?
While it's possible to store access tokens in memory or cookies, refresh tokens should be stored using secure httpOnly cookies or in a server-side database to reduce the risk of token theft.
How do you revoke a specific user's access tokens?
The refresh token mechanism allows the server to maintain a list of valid tokens. To revoke access, remove the refresh token from the active token list. The next request to refresh an access token will fail.
What's the main difference between access tokens and refresh tokens?
Access tokens are short-lived, allowing users to authorize actions temporarily without repeated credential checks. Refresh tokens are long-lived and are used to issue new access tokens securely.