JWT-Based Authentication and Authorization System in Node.js
1. User Registration:
- Users provide registration details (e.g., username, email, password) through a registration form.
- Server receives the registration request, validates the input data, and securely hashes the password before storing it in the database.
- Upon successful registration, the server responds with a success message or redirects the user to the login page.
2. User Authentication:
- Users provide login credentials (username/email and password) through a login form. The server validates the credentials, checks the user's existence, and verifies the provided password against the stored hashed password.
- If authentication is successful, the server generates a JWT token containing the user's ID and any necessary claims signs it with a secret key, and sends it back to the client.
- The client stores the JWT token (usually in local storage or a cookie) for subsequent requests.
3. Accessing Protected Routes:
- Client includes the JWT token in the Authorization header of HTTP requests to access protected routes.
- Server middleware intercepts the requests to protected routes and verifies the JWT token's authenticity, signature, and expiration.
- If the token is valid, the server extracts the user's ID from the token and attaches it to the request object for further processing.
- If the token is invalid or expired, the server responds with a 401 Unauthorized error, prompting the client to reauthenticate.
4. Authorization:
- Server middleware checks the user's permissions and role based on the user's ID extracted from the JWT token.
- If the user has the necessary permissions, the server allows access to the requested resource or endpoint.
- If the user lacks sufficient permissions, the server responds with a 403 Forbidden error, indicating that the user is not authorized to access the resource.
5. Token Refresh (Optional):
- Client periodically checks the JWT token's expiration time.
- If the token is about to expire, the client sends a token refresh request to the server.
- Server validates the existing JWT token, generates a new token with a later expiration time, and sends it back to the client.
- The client replaces the old token with the new one for future requests.
6. Logging Out:
- When the user logs out, the client clears the stored JWT token.
- Optionally, the server may invalidate the JWT token on logout by maintaining a blacklist of revoked tokens or by implementing token expiration strategies.
Additional Considerations:
- Install necessary packages (
jsonwebtokenfor JWT operations,bcryptfor password hashing). - Securely store sensitive information like passwords (hashed) and JWT secret keys using environment variables.
- Use HTTPS to encrypt data transmitted between the client and server to ensure security.
- Implement rate limiting to prevent brute-force attacks on the authentication endpoint.
- Implement logging and monitoring for security auditing purposes.
- Follow security best practices such as using strong JWT secret keys and regularly updating dependencies.
By following these steps and best practices, you can design a robust and secure JWT-based authentication and authorization system in Node.js.
// Import necessary packages
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// Initialize Express app
const app = express();
app.use(express.json());
// Mock database for demonstration purposes
const users = [];
// Secret key for JWT
const JWT_SECRET = 'your_secret_key';
// Middleware to verify JWT token
function verifyToken(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ message: 'Unauthorized: No token provided' });
}
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Unauthorized: Invalid token' });
}
req.user = decoded;
next();
});
}
// Register route
app.post('/register', async (req, res) => {
try {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = { username, password: hashedPassword };
users.push(user);
res.status(201).json({ message: 'User registered successfully' });
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
}
});
// Login route
app.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
const user = users.find(user => user.username === username);
if (!user) {
return res.status(401).json({ message: 'Invalid username or password' });
}
if (await bcrypt.compare(password, user.password)) {
const token = jwt.sign({ username: user.username }, JWT_SECRET);
res.json({ token });
} else {
res.status(401).json({ message: 'Invalid username or password' });
}
} catch (error) {
res.status(500).json({ message: 'Internal server error' });
}
});
// Protected route
app.get('/profile', verifyToken, (req, res) => {
res.json({ message: 'Protected route accessed successfully', user: req.user });
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});In this example:
- We use Express.js to handle HTTP requests.
- We use
bcryptfor password hashing andjsonwebtokenfor JWT operations. - The
/registerroute handles user registration by hashing the password and storing it in theusersarray (mock database). - The
/loginroute authenticates users by comparing the hashed password with the stored password. If the credentials are valid, a JWT token is generated and sent back to the client. - The
/profileroute is a protected route that requires a valid JWT token for access. TheverifyTokenmiddleware verifies the token before granting access to the route. - The server listens on port 3000 by default.