Sling Academy
Home/Node.js/Node.js & Express: Implementing Route Guards with Middleware (3 Ways)

Node.js & Express: Implementing Route Guards with Middleware (3 Ways)

Last updated: December 28, 2023

Some different ways to add route guards to your Express app to protect private routes.

1. Basic Authentication Middleware

A simple user authentication guard can be implemented using middleware functions that check for valid credentials before granting access to a private route.

This solution checks the request headers for a basic authentication token. The middleware decodes this token to validate the user’s credentials against a user store or database before allowing access to the route.

Steps to Implement:

  • Create a basic authentication middleware function.
  • Validate the authentication header against known user credentials.
  • If valid, call next() to proceed to the route handler; if not, return an unauthorized error.

Code example:

const express = require('express');
const app = express();

function authenticateUser(req, res, next) {
    const authHeader = req.headers['authorization'];
    if (!authHeader) {
        return res.status(401).send('Authentication required');
    }
    // Decode and validate authHeader; just an example check
    if (authHeader === 'Basic VALID_TOKEN') {
        return next();
    }
    return res.status(401).send('Invalid credentials');
}

app.use('/private', authenticateUser);
app.get('/private', (req, res) => {
    res.send('Access to private route successful');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Pros and Cons:

  • Pros: Simple to implement; doesn’t require external libraries.
  • Cons: Not suitable for modern web applications; lacks support for token-based authentication schemes like OAuth or JWT.

2. JWT Token Verification Middleware

This approach uses JSON Web Tokens (JWT) for route authentication, commonly used for stateless authentication in modern web applications.

Description: A middleware function that validates a JWT token sent by the client in the headers. If the token is valid and not expired, the client is granted access to the route.

Steps to Implement:

  • Use a library like jsonwebtoken to verify the JWT token.
  • Extract the token from the authorization header.
  • Verify the token and its expiration.
  • Allow access to the next middleware or route handler upon successful verification.

Code example:

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();

function verifyToken(req, res, next) {
    const token = req.headers['authorization']?.split(' ')[1];
    if (!token) {
        return res.status(403).send('A token is required for authentication');
    }
    try {
        const decoded = jwt.verify(token, 'secretKey');
        req.user = decoded;
    } catch (err) {
        return res.status(401).send('Invalid Token');
    }
    return next();
}

app.use('/private', verifyToken);
app.get('/private', (req, res) => {
    res.send('Access to private route with token successful');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Pros and Cons:

  • Pros: Secure and stateless, supports modern authentication workflows.
  • Cons: Requires additional setup for token generation and management, dependency on third-party libraries.

3. Role-Based Access Control Middleware

In addition to user authentication, this solution introduces role-based access control (RBAC) to protect sensitive routes only for certain user roles.

Description: Middleware that checks the authenticated user’s role and determines if they have the necessary permissions to access the route.

Steps to Implement:

  • Create a middleware function that verifies the user role.
  • After user authentication, check if the user’s role matches the required role for the route.
  • If there’s a match, proceed to the route handler; otherwise, return a forbidden error.

Code example:

const express = require('express');
const app = express();

// A mock function to get user data based on the token
const getUser = (token) => {
    // Assume we have a function to decode the token and retrieve user data
    return { role: 'admin' }; // simply for example purposes
};

function roleCheck(req, res, next) {
    const token = req.headers['authorization']?.split(' ')[1];
    if (!token) {
        return res.status(403).send('A token is required for authentication');
    }
    const user = getUser(token);
    if (user.role !== 'admin') {
        return res.status(403).send('Insufficient permissions');
    }
    return next();
}

app.use('/admin', roleCheck);
app.get('/admin', (req, res) => {
    res.send('Admin access granted');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Pros and Cons:

  • Pros: Enhances security by enforcing access based on user roles, flexible permission management.
  • Cons: Requires a well-defined user role system, additional complexity in middleware management.

Conclusion

Protecting private routes in Node.js and Express applications is essential for maintaining application security and data integrity. By using route guards implemented as middleware, developers can ensure that only authenticated and authorized users can access sensitive parts of an application.

The solutions provided range from basic authentication to more complex token-based and role-based checks. The choice of which solution to use will depend on the specific requirements and complexity of the application. By combining these methods appropriately and keeping up with best practices in authentication and authorization, developers can create robust security mechanisms for their Express-based applications.

Next Article: How to Integrate H2 Database with Express and Node.js

Previous Article: How to Manage Cookies in Express JS

Series: Node.js & Express Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates