Agile Coder Logo
AgileCoder
Beginner

Securing API: Restrict Access to Admins Only in ExpressJS

API security ensures data protection by restricting sensitive operations to admins and validating user sessions. Middleware like allowAdminsOnly enforces admin-only access, while authorise verifies tokens and trusted devices for robust security across web and mobile clients.

Smruti Ranjan
January 10, 2025
4 min read
#NodeJS#Web Server
Securing API: Restrict Access to Admins Only in ExpressJS
Advertisement

API security is a critical aspect of modern web applications. Restricting sensitive operations to authorized users, such as administrators, ensures data protection and system integrity. This blog will demonstrate how to secure your API by implementing middleware to allow only admins and an authorization mechanism to validate user sessions and trusted devices.

Middleware for Admin-Only Access

The allowAdminsOnly middleware ensures that only users with an admin role can access specific routes. Here’s the implementation:

Typescript
export const allowAdminsOnly = (req: Request, res: Response, next: NextFunction) => {
  try {
    const user = (req as ExtendedRequest).user;
    if (!user) {
      return res.status(401).json({ error: "Unauthorized. Please log in." });
    }

    if (user.user_role === userTypes.ADMIN) {
      return next();
    }

    return res.status(403).json({ error: "You are not an admin" });
  } catch (error) {
    console.error("Error in checking admin role:", error);
    res.status(500).json({ error: "Internal server error" });
  }
};
↕ click to expand

Key Features

  • Checks if the user is logged in.
  • Verifies if the logged-in user has the ADMIN role.
  • Returns appropriate HTTP status codes for unauthorized or forbidden access.

Authorization Middleware

The authorise middleware validates user sessions and ensures that requests are made from trusted devices. Here’s the implementation:

typescript
export const authorise = (req: Request, res: Response, next: NextFunction) => {
  try {
    let accessToken;
    let deviceId;
    const isMobileApp = req.headers["client-type"] === "mobile_app";

    if (isMobileApp) {
      accessToken = req.headers.authorization?.split(" ")[1];
      deviceId = req.headers.deviceId;
    } else {
      accessToken = req.cookies?.accessToken;
      deviceId = req.cookies?.deviceId;
    }

    if (!accessToken) {
      return res.status(401).json({ error: "User not logged in" });
    }

    jwt.verify(accessToken, process.env.SECRET_KEY || "", async (err: any, decoded: any) => {
      if (err) {
        return res.status(403).json({ error: "Invalid access token" });
      }

      const device = await TrustedDeviceModel.findOne({
        user_id: decoded.userId,
        device_id: deviceId,
        isActive: true,
      });

      if (!device) {
        return res.status(401).json({ error: "Invalid or inactive device. Please Login Again" });
      }

      (req as ExtendedRequest).user = decoded;
      next();
    });
  } catch (error) {
    console.error("Error in authorise middleware:", error);
    res.status(500).json({ error: "Internal server error" });
  }
};
↕ click to expand

Key Features

  • Supports both mobile app and web clients.
  • Verifies the access token and ensures it is valid.
  • Checks the device’s validity using a trusted device database.

To implement these middlewares, we’ll use a streamlined user model. Here’s a cleaned-up version of the user schema:

TypeScript
const userSchema = new Schema({
  username: {
    type: String,
    unique: true,
    required: false,
    index: true,
  },
  full_name: {
    type: String,
    required: true,
  },
  email_address: {
    type: String,
    unique: true,
    required: true,
    index: true,
  },
  user_role: {
    type: String,
    required: true,
    enum: ["admin", "user"],
    default: "user",
    index: true,
  },
});

const UserModel = model<UserDocument, UserModel>("User", userSchema);
↕ click to expand

Key Fields

  • username, full_name, and email_address: Basic user details.
  • user_role: Differentiates between admin and regular users.

Using the Middlewares

To protect your routes, simply apply the middleware:

TypeScript
const app = express();

// Route accessible only by admins
app.get("/admin", authorise, allowAdminsOnly, (req, res) => {
  res.send("Welcome, Admin!");
});

// Public route
app.get("/public", (req, res) => {
  res.send("Welcome, Guest!");
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});
↕ click to expand

By implementing the allowAdminsOnly and authorise middlewares, you can effectively secure your API. The admin-only restriction ensures sensitive operations are safeguarded, while the authorization process validates users and devices, adding another layer of security.

Integrate these techniques into your Node.js application to protect your resources and maintain robust API security.

That's it for today. See ya 👋

Advertisement

Behind-the-build, in your inbox.

New tutorials, book updates, and behind-the-scenes notes from the studio. No schedule, no spam.

Comments

Leave a comment

Comments are moderated before appearing.