DEV Community

Stillnoturdad
Stillnoturdad

Posted on

Middlewares For Baby Programmers

In a secure web application, it's crucial to ensure that only authenticated users can access certain routes. Additionally, specific actions, such as modifying or deleting data, should only be allowed for users with proper permissions. This is where authentication and authorization come into play.

In this article, we’ll break down a simple implementation of authentication and authorization middleware in a Node.js application using Express.

1. Authentication Middleware

Authentication is the process of verifying that a user is who they claim to be. In this example, we use a JWT (JSON Web Token) to validate the user's identity before they can access protected routes.

const authentication = async (req, res, next) => {
    try {
        let { authorization } = req.headers;
        if (!authorization) throw { name: "InvalidToken" }; // No token provided

        const [bearer, token] = authorization.split(" ");
        if (bearer !== "Bearer") throw { name: "InvalidToken" }; // Invalid token format

        const payload = verifyToken(token); // Verify the token and get user data
        const member = await Member.findByPk(payload.id); // Find the user in the database

        if (!member) throw { name: "InvalidToken" }; // No matching user found

        req.member = { id: member.id }; // Attach user data to the request
        next(); // Pass to the next middleware or route handler
    } catch (error) {
        next(error); // Handle errors
    }
};
Enter fullscreen mode Exit fullscreen mode

The token is expected to be in the Authorization header (formatted as Bearer token).
The middleware verifies if the token is present and properly formatted.
Using verifyToken(), it checks if the token is valid and decodes the user's data.
The user's ID from the token payload is used to find them in the database.
If successful, the user's ID is attached to the request (req.member), allowing other routes to know who the user is.
If the token is missing, invalid, or the user doesn’t exist, an error is thrown.

2. Authorization Middleware

Authorization controls what actions an authenticated user is allowed to perform. In this case, the authorization middleware ensures that a user can only interact with tickets they own.

const authorization = async (req, res, next) => {
    try {
        let recipientId = req.member.id; // The authenticated user's ID
        let ticketId = req.params.id; // The ticket ID from the request

        let ticket = await EventTicket.findByPk(ticketId); // Find the ticket in the database
        if (!ticket) throw { name: "Custom", status: 404, message: "Ticket not found" }; // If ticket doesn't exist
        if (recipientId !== ticket.recipientId) throw { name: "Unauthorized" }; // If user doesn't own the ticket

        next(); // If authorized, proceed to the next middleware or route handler
    } catch (error) {
        next(error); // Handle errors
    }
};
Enter fullscreen mode Exit fullscreen mode

The middleware retrieves the authenticated user’s ID (req.member.id) and the ticket ID from the request parameters (req.params.id).
It checks if the ticket exists in the database.
It ensures that the logged-in user (the recipient) is the owner of the ticket. If the user doesn’t own the ticket, an "Unauthorized" error is thrown.
If the user is authorized, the middleware passes control to the next handler.

3. Error Handling Middleware

The error-handling middleware ensures that errors caught during the request lifecycle are properly handled and returned as responses.

app.use((error, req, res, next) => {
    let status = error.status || 500; // Default to 500 if status isn't provided
    let message = error.message || "Internal server error"; // Default message

    switch (error.name) {
        case "SequelizeValidationError":
        case "SequelizeUniqueConstraintError":
            status = 400;
            message = error.errors[0].message; // Handle Sequelize validation errors
            break;
        case "InvalidToken":
        case "JsonWebTokenError":
            status = 401;
            message = "Invalid token"; // Handle token errors
            break;
        case "Unauthorized":
            status = 403;
            message = "You are not authorized"; // Handle unauthorized access
            break;
    }
    res.status(status).json({ message }); // Send error response
});
Enter fullscreen mode Exit fullscreen mode

How It Works:
This middleware catches any errors passed down by the next(error) function.
Depending on the error type, it sets the appropriate status code (e.g., 401 for invalid tokens, 403 for unauthorized actions, etc.).
A relevant message is returned to the client, helping users understand what went wrong.

Top comments (0)