Overview
Authentication and authorization are critical components of web application security. They ensure that only legitimate users can access sensitive resources and perform actions. This tutorial will walk you through the implementation of authentication and authorization in an Express.js application using JSON Web Tokens (JWT). We’ll start with the basics and progress to more advanced topics, including token generation, verification, and the secure handling of sessions.
Getting Started
To begin, make sure you have Node.js and npm installed. Create a new directory for your project and navigate to it in your terminal. Initialize a new npm project with npm init
.
$ mkdir auth-jwt-app
$ cd auth-jwt-app
$ npm init -y
Next, install Express.js and required packages:
$ npm install express jsonwebtoken bcryptjs body-parser
Create a file named server.js
and set up the basic Express server:
const express = require('express');
const app = express();
app.use(express.json());
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Implementing Authentication
First, we’ll set up the user signup route. This example uses a simple in-memory array to store user records. In production, you would typically use a database.
// At the top of your server.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
let users = [];
// Signup route
app.post('/signup', async (req, res) => {
const { username, password } = req.body;
// Hash password
const hashedPassword = await bcrypt.hash(password, 8);
// Store user
users.push({ username, password: hashedPassword });
res.status(201).send('User created');
});
Generating JWT
When a user logs in, we will generate a JWT and send it back to the client:
// At the top of your server.js
const secretKey = 'your_secret_key';
// Login route
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).send('Invalid credentials');
}
// Generate token
const token = jwt.sign({ userId: user.username }, secretKey, { expiresIn: '1h' });
res.status(200).send({ token });
});
Authorization Middleware
With the JWT generated, we must now verify this token on each protected route. We’ll create middleware to check the validity of the token:
// Middleware for token verification
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.status(401).send('Token required');
jwt.verify(token, secretKey, (err, user) => {
if (err) return res.status(403).send('Invalid or expired token');
req.user = user;
next();
});
};
// Protected route example
app.get('/dashboard', authenticateToken, (req, res) => {
res.status(200).send('Welcome to the dashboard, ' + req.user.userId);
});
Advanced Considerations
We’ve covered the basics, now let’s address some advanced scenarios such as refresh tokens for persistent sessions, and role-based authorization.
To enable refresh tokens, we need an endpoint that can issue a new JWT when the current token is about to expire. This requires us to store refresh tokens securely, often in a persistent database.
Conclusion
In this tutorial, we’ve established a strong foundation for handling authentication and authorization in your Express.js applications using JWT. While our examples have been simplistic, they provide the building blocks for creating a secure authentication system. Remember to always keep your secret keys private, validate your tokens consistently, and consider additional security measures for production environments.