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:
- Assigning roles to users during registration
- Implementing role-based access control (RBAC)
- Protecting routes based on user roles
- 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
- Open
models/User.js
and add arole
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) andadmin
. - enum: Restricts the role field to only accept values of either
user
oradmin
.
Step 2: Assigning Roles During User Registration
Next, let’s modify the registration route to allow an admin to assign roles to new users.
Updating the Registration Route
- Open
routes/users.js
and modify the registration route to include the optionalrole
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
- Inside your
middleware
folder, create a new file calledrole.js
:
touch middleware/role.js
- 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.
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.
- Open
routes/users.js
and modify a route to protect it with theroleCheck
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 theadmin
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
.
- Protect a route accessible by both
admin
anduser
:
// 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
andadmin
roles. TheroleCheck(['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
- 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"
}
- You should receive a response confirming the admin user has been created.
2. Register a Regular User
- 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"
}
- You should receive a response confirming the user has been created.
3. Log In as the Admin User
- Send a POST request to
http://localhost:5000/api/users/login
with the following JSON body:
{
"email": "[email protected]",
"password": "adminpassword"
}
- You should receive a response containing the JWT token.
4. Access Admin-Only Route
- 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
- 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
- Log in as the regular user using the same steps as above but with the regular user’s credentials.
- 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!