Day 7: Securing User Authentication and Authorization with Role-Based Access Control (RBAC)


In this tutorial, you’ll learn how to add more advanced authentication and authorization to your Express API. By the end of today’s lesson, users will be assigned roles (e.g., admin, user), and access to certain routes will be restricted based on those roles.


What You Will Learn Today:

  1. Assigning roles to users during registration
  2. Implementing role-based access control (RBAC)
  3. Protecting routes based on user roles
  4. Testing role-based access control using Postman

Step 1: Extending the User Model with Roles

To implement role-based access control (RBAC), we first need to assign roles to users. In this tutorial, we’ll use two basic roles: admin and user. You can easily extend this to more roles if needed.

Modifying the User Model

  1. Open models/User.js and add a role field to the user schema:
const mongoose = require('mongoose');

// Define the user schema
const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user', // Default role is 'user'
  },
});

const User = mongoose.model('User', userSchema);

module.exports = User;

Explanation:

  • role: This field indicates the user’s role. We’ve added two possible roles: user (the default) and admin.
  • enum: Restricts the role field to only accept values of either user or admin.

Step 2: Assigning Roles During User Registration

Next, let’s modify the registration route to allow an admin to assign roles to new users.

See also  Day 3: Asynchronous State Management with Redux Thunk

Updating the Registration Route

  1. Open routes/users.js and modify the registration route to include the optional role field:
// Register a new user with role assignment
router.post(
  '/register',
  [
    check('name', 'Name is required').not().isEmpty(),
    check('email', 'Please include a valid email').isEmail(),
    check('password', 'Password must be at least 6 characters long').isLength({ min: 6 }),
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { name, email, password, role } = req.body;

    try {
      const existingUser = await User.findOne({ email });
      if (existingUser) {
        return res.status(400).json({ message: 'User already exists' });
      }

      const hashedPassword = await bcrypt.hash(password, 10);
      const newUser = new User({
        name,
        email,
        password: hashedPassword,
        role: role || 'user', // Default role is 'user' if not provided
      });

      const savedUser = await newUser.save();
      res.status(201).json(savedUser);
    } catch (error) {
      res.status(500).json({ message: 'Error registering user', error });
    }
  }
);

Explanation:

  • We added an optional role field in the registration request. If no role is provided, the default role (user) is assigned.
  • This allows an admin to specify the role when creating new users.

Step 3: Implementing Role-Based Access Control (RBAC)

Now that users can be assigned roles, let’s implement role-based access control (RBAC). This will restrict access to certain routes based on the user’s role.

Creating a Middleware for Role Checking

  1. Inside your middleware folder, create a new file called role.js:
touch middleware/role.js
  1. Open middleware/role.js and define the role-checking middleware:
const roleCheck = (requiredRoles) => {
  return (req, res, next) => {
    if (!requiredRoles.includes(req.user.role)) {
      return res.status(403).json({ message: 'Access denied. Insufficient permissions.' });
    }
    next();
  };
};

module.exports = roleCheck;

Explanation:

  • roleCheck: This middleware takes an array of required roles as an argument. It checks if the current user’s role (from req.user.role) matches one of the required roles.
  • If the user’s role is not included in the required roles, the middleware returns a 403 Forbidden response.
See also  Day 8: Storing Data Locally Using AsyncStorage

Protecting Routes with Role-Based Access Control

Let’s apply this middleware to some routes, such as one that’s only accessible by admin users.

  1. Open routes/users.js and modify a route to protect it with the roleCheck middleware:
const auth = require('../middleware/auth');
const roleCheck = require('../middleware/role');

// Get all users (admin-only route)
router.get('/', auth, roleCheck(['admin']), userController.getAllUsers);

Explanation:

  • We’ve added the roleCheck(['admin']) middleware to the GET /api/users route, which means only users with the admin role can access this route.
  • auth middleware ensures the user is authenticated, and then roleCheck ensures they have the required role.

Step 4: Protecting Other Routes Based on Roles

Let’s add role-based protection to other routes. For example, you might want to allow both admin and user roles to access certain routes, while others are reserved only for admin.

  1. Protect a route accessible by both admin and user:
// Get user profile (accessible by all logged-in users)
router.get('/profile', auth, roleCheck(['user', 'admin']), (req, res) => {
  res.json({ message: `Welcome ${req.user.name}, this is your profile.` });
});

Explanation:

  • The GET /api/users/profile route is accessible by both user and admin roles. The roleCheck(['user', 'admin']) middleware ensures that only authenticated users with the correct roles can access the route.

Step 5: Testing Role-Based Access Control with Postman

Let’s test role-based access control using Postman.

1. Register a New Admin User

  1. In Postman, send a POST request to http://localhost:5000/api/users/register with the following JSON body:
{
  "name": "Admin User",
  "email": "[email protected]",
  "password": "adminpassword",
  "role": "admin"
}
  1. You should receive a response confirming the admin user has been created.

2. Register a Regular User

  1. Send a POST request to http://localhost:5000/api/users/register with the following JSON body:
{
  "name": "Regular User",
  "email": "[email protected]",
  "password": "userpassword",
  "role": "user"
}
  1. You should receive a response confirming the user has been created.
See also  Day 1: Introduction to TensorFlow.js or Core ML for Mobile Apps

3. Log In as the Admin User

  1. Send a POST request to http://localhost:5000/api/users/login with the following JSON body:
{
  "email": "[email protected]",
  "password": "adminpassword"
}
  1. You should receive a response containing the JWT token.

4. Access Admin-Only Route

  1. Send a GET request to http://localhost:5000/api/users and include the JWT token in the Headers as:
x-auth-token: your_jwt_token_here
  1. You should receive a list of users because the admin user has access to this route.

5. Log In as the Regular User and Access the Admin Route

  1. Log in as the regular user using the same steps as above but with the regular user’s credentials.
  2. Try to access the GET /api/users route with the regular user’s token.

You should receive a 403 Forbidden response because the regular user does not have the admin role.


Step 6: Recap and Summary

In this tutorial, you learned how to implement role-based access control (RBAC) in your Express API. Here’s a quick summary of what you’ve done:

  • Extended the user model to include a role field.
  • Allowed roles to be assigned during registration.
  • Created a role-checking middleware to protect routes based on user roles.
  • Applied role-based access control to specific routes.
  • Tested the RBAC functionality using Postman by accessing protected routes with different roles.

With role-based access control, you can now manage access to different parts of your API depending on the user’s role.


**Next Up: Day

8 – Storing Data Securely and Implementing File Uploads**

In Day 8, we’ll focus on securely storing user data and implementing file uploads (e.g., for images and documents) in your API.

Stay tuned for more advanced features tomorrow!


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.