Securing an API involves two distinct concepts:
- Authentication (AuthN): The process of verifying a user's identity. "Are you who you say you are?" This is typically done with a username and password.
- Authorization (AuthZ): The process of verifying that an authenticated user has permission to access a specific resource. "Are you allowed to do this?"
For modern APIs, the most common way to handle this is with JSON Web Tokens (JWT).
The JWT Authentication Flow
JWT provides a stateless authentication mechanism. The server doesn't need to store session information in memory or a database.
- Login: The user sends their credentials (e.g., email/password) to a /login endpoint.
- Verification: The server validates the credentials against the database.
- Token Creation: If the credentials are valid, the server creates a JWT. A JWT consists of three parts: a header, a payload (containing user info like ID and role), and a signature. The signature is created using a secret key known only to the server.
- Token Issuance: The server sends the signed JWT back to the client.
- Token Storage: The client stores the JWT (usually in memory or localStorage).
- Authenticated Requests: For every subsequent request to a protected route, the client includes the JWT in the Authorization header, typically as Bearer <token>.
- Token Verification: The server receives the request, extracts the token, and verifies its signature using the secret key. If the signature is valid, the server trusts the information in the payload and processes the request. If not, it rejects the request with a 401 Unauthorized error.
Implementation Example
First, install the necessary libraries:
Bash
npm install jsonwebtoken bcryptjs # bcryptjs is for securely hashing passwords
1. Login Route to Create a Token
JavaScript
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());
// In a real app, you get this from environment variables!
const JWT_SECRET = 'your-super-secret-key-that-is-very-long';
app.post('/login', (req, res) => {
// --- In a real app, you'd do this: ---
// 1. Get email/password from req.body
// 2. Find the user in the database by email
// 3. Compare the provided password with the stored hashed password using bcrypt.compare()
// For this example, let's assume authentication is successful
const user = { id: 123, username: 'testuser', role: 'user' };
// Create the JWT payload
const payload = {
userId: user.id,
role: user.role,
};
// Sign the token
const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '1h' }); // Token expires in 1 hour
res.json({ success: true, token: token });
});
2. Middleware to Protect Routes
This middleware will check for a valid token on incoming requests.
JavaScript
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (token == null) {
return res.sendStatus(401); // Unauthorized
}
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
return res.sendStatus(403); // Forbidden (invalid token)
}
// If token is valid, attach the user payload to the request object
req.user = user;
next();
});
}
// Use the middleware to protect a route
app.get('/profile', authenticateToken, (req, res) => {
// Thanks to the middleware, req.user is now available
res.json({ message: `Welcome user ${req.user.userId}!` });
});
Now, the /profile endpoint can only be accessed by clients who provide a valid JWT.