The page is loading...

Creating a Role-Based User Authentication System with Angular, Express and MySQL — Part 2: Finishing the Backend

In this post, we will finish our application’s RESTful API by introducing user roles, routing and new endpoints so new users can register, authenticate and access routes based on their role.

This post is the second part of the Creating a Role-Based User Authentication System with Angular, Express and MySQL series.

GitHub Repository

The code from this post is available on the part-2 branch in the project’s GitHub repository.

View on GitHub

Routing Structure

Our app has a single home route inside our server.js file. That was great for testing purposes but unfortunately — for LOTR fans —, we will have to replace it with a better structure for our routing system.

We won’t need the Express router to handle any other routes than our API’s endpoints. For our frontend, we will need it to serve our initial index.html file and from there on we’ll let Angular take the lead.

Let’s jump right into it and replace the home / route with the following:

# app/server.js
// ...

// Bundle API routes.
app.use('/api', require('./routes/api')(passport));

// Catch all route.
app.get('*', function(req, res) {
    res.sendFile(path.join(__dirname + '../../public/app/views/index.html'));
});

// ...

The code above does two simple things:

  1. It requires our API’s routing function in which we pass the passport instance as a parameter. It then bundles all routes under /api, a very common API endpoint structure.

  2. It creates a catch-all route that always responds with an index.html file. As you can see, we’re not using any view engine here as it’s out of the scope of this tutorial, but feel free and set-up Pug or any other Express compliant view engine.

Inside the /public/app/views folder create a new file called index.html.

# /public/app/views/index.html
<!doctype html>
<html class="no-js" lang="en">
 <head>
 <meta charset="utf-8">
 <meta http-equiv="x-ua-compatible" content="ie=edge">
 <title>JAMES Authentication</title>
 <meta name="description" content="Authentication system using JWTs, Angular, MySQL, Express and the Sequelize ORM.">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
 </head>
 <body>
 <p>Well, hello there!</p>
 
 <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.min.js"></script>
 </body>
</html>

We don’t have the /routes/api.js file, let’s fix that.

API Routes

Inside /routes/api.js we can now add some boilerplate code for our API routing function.

# routes/api.js
'use strict';

var router = require('express').Router();

var config = require('../config');

var APIRoutes = function(passport) {
    // TODO: Create API routes.
};

module.exports = APIRoutes;

This is the file where we will orchestrate our entire application’s routing. It’s not doing much yet other than initializing the router, requiring the config file and finally creating and exporting the APIRoutes function.

A small thing to note is that the APIRoutes function accepts the passport instance as a param — which we passed earlier.

The // TODO: comment looks boring as hell, don’t you think? Let’s replace it with something fun. I was thinking about the endpoint for creating a new account.

Registering Users

Inside our APIRoutes function let’s create the signup endpoint.

# routes/api.js
// ...

router.post('/signup', AuthController.signUp);

return router;

// ...

We use the Express router to register a new POST request to the /signup endpoint and pass as a second argument the AuthController.signUp method.

Finally, we return the router instance which is handled as middleware inside server.js when we’re nesting all routes under /api.

Now, when we’ll make a POST request to the http://localhost:8080/api/signup endpoint, the AuthController.signUp method should run. The problem is that we don’t have any AuthController object with a signUp method available yet.

When a POST request will be made to the request to the /api/signup endpoint, the AuthController.signUp method should run.

It won’t work for now as we haven’t created the AuthController yet. Let’s create a new file called authController.js inside our app/controllers folder and then require it in the api.js file.

# routes/api.js
'use strict';

var router = require('express').Router();

var config = require('../config'),
    AuthController = require('../controllers/authController');

// ...

Inside the authController.js file, we can now add some boilerplate code:

# controllers/authController.js
'use strict';

var jwt = require('jsonwebtoken');

var config = require('../config'),
    db = require('../services/database'),
    User = require('../models/user');

// The authentication controller.
var AuthController = {};

// Register a user.
AuthController.signUp = function(req, res) {
    // TODO: Register new users.
}

module.exports = AuthController;

We first require the jsonwebtoken library, then the config file, the database service, and the User model.

At the end, we create the AuthController object and its signUp method which finally end up being exported.

We can proceed by adding the missing logic from the signUp method.

# controllers/authController.js
// ...

AuthController.signUp = function(req, res) {
    if(!req.body.username || !req.body.password) {
        res.json({ message: 'Please provide a username and a password.' });
    } else {
        db.sync().then(function() {
            var newUser = {
                username: req.body.username,
                password: req.body.password
            };

            return User.create(newUser).then(function() {
                res.status(201).json({ message: 'Account created!' });
            });
        }).catch(function(error) {
            res.status(403).json({ message: 'Username already exists!' });
        });
    }
}

// ...

That’s a lot to take in as a single bite. Let’s break it down and analyze it:

First, we check if the username and password are available on the request body and respond with a friendly message if that’s not the case letting the user know that both should be provided.

This is a great place to do in-depth checks. For example, you could ask the user for a second password and check if both are identical to avoid any typing errors and potential login issues afterward.

If everything is fine and all required data is provided, we sync the db and then attempt to create a new user. If the user is created successfully, we respond with a message, otherwise, we let the ‘user’ know that there was a problem during the attempt. In this case, we let the person know that the username already exists.

We can restart our server and test the new endpoint. As you can see below, I managed to create a new user using Postman and received the expected response.

Checking the database confirms the account was successfully created.

Excellent! We now have a working sign-up endpoint and people can create new accounts.

Well, not really, but it’s something.

Authenticating Users

It’s no fun to just create an account. We want to log in and use it to do something fun.

Inside our routes/api.js file, right below our signup endpoint, let’s create a new one to let people log in with their freshly created accounts.

# routes/api.js
// ...

router.post('/authenticate', AuthController.authenticateUser);

// ...

Inside our AuthController let’s create the authenticateUser method.

# controllers/authController.js
// ...

AuthController.authenticateUser = function(req, res) {
    if(!req.body.username || !req.body.password) {
        res.status(404).json({ message: 'Username and password are needed!' });
    } else {
        var username = req.body.username,
            password = req.body.password,
            potentialUser = { where: { username: username } };

        User.findOne(potentialUser).then(function(user) {
            if(!user) {
                res.status(404).json({ message: 'Authentication failed!' });
            } else {
                user.comparePasswords(password, function(error, isMatch) {
                    if(isMatch && !error) {
                        var token = jwt.sign(
                            { username: user.username },
                            config.keys.secret,
                            { expiresIn: '30m' }
                        );

                        res.json({ success: true, token: 'JWT ' + token });
                    } else {
                        res.status(404).json({ message: 'Login failed!' });
                    }
                });
            }
        }).catch(function(error) {
            res.status(500).json({ message: 'There was an error!' });
        });
    }
}

// ...

First, we check if the username and password are provided. If not, we let the ‘user’ know.

If both credentials are provided, we then try to find a single user where the username matches the one provided by the person attempting to log-in. If no user is found, we respond letting them know.

Having a user found in the database matching the request, we then proceed to comparePasswords using the model instance method we created in the previous article. If the passwords match, we then use the jsonwebtoken library to sign the token using the secret key stored in our config file, setting the username as a payload and setting the expiration time to 30 minutes.

Tip: It’s considered a best practice to store the token expiration time inside the config file.

If the passwords do not match, we let the ‘user’ know about the failed login attempt. We also attempt to catch any potential error if something else goes wrong.

We wrote a lot of code, let’s get back to Postman and make sure everything works as expected and we can login with the user account created earlier. If everything went well, your response should look similar to the screenshot below.

Perfect, we are now able to log in and use the token provided by the server.

Following we will continue to set up the user roles and create a new route for each user type. Crazy fun stuff!

User Roles

Currently, we have only one generic user type and no way of grouping users. It’s no fun having an application where anyone can access everything. We want to restrict access to specific routes based on user role so our annoying neighbor couldn’t create an account, log in and change our application’s settings in the future.

At the end of our config.js file, we need to define the user roles and the access levels for each user role. For our little app, we will create only three user types: Guests, (registered) users and admins.

# app/config.js
// ...

var userRoles = config.userRoles = {
    guest: 1,    // ...001
    user: 2,     // ...010
    admin: 4     // ...100
};

config.accessLevels = {
    guest: userRoles.guest | userRoles.user | userRoles.admin,    // ...111
    user: userRoles.user | userRoles.admin,                       // ...110
    admin: userRoles.admin                                        // ...100
};

We first define the three roles needed with values when converted to 32-bit integers, translate to the values added in the comments.

Then we define the access level for each area based on user role using the bitwise OR operator.

  1. The guest area can be accessed by all three user types, guests, registered users and admins.
  2. The user area can be accessed only by registered users and admins.
  3. The admin area can be accessed only by admin users.

By doing this we added a clear distinction in how we want to group our routes based on access level and user role. We can also extend it in the future by adding new roles or access levels if the business requirements introduce new functionality.

This is great! We are now almost ready to create our first protected route for registered users or admins. Before that, we still have a few small steps to make which consists of modifying the User model and creating a helper function that checks the user role before running the controller’s callback hooked to our route.

Updating the User Model

Before anything else, we need to update our User schema to reflect the role changes in the model definition.

# app/models/user.js
// ...

var modelDefinition = {
    username: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
    },

    password: {
        type: Sequelize.STRING,
        allowNull: false
    },

    role: {
        type: Sequelize.INTEGER,
        defaultValue: config.userRoles.user
    }
};

// ...

We set the role type as a Sequelize.INTEGER which is enough for our neeeds and set the defaultValue to a basic user role.

Make sure you drop the existing users table as we haven’t implemented migrations or written any code to alter the table as it’s out of the scope of this tutorial. Skipping this step will result in errors when creating new users.

Role Checking

Inside our app/services folder, create a new file called routesHelper.js and inside let’s add our function.

# app/services/routesHelper.js
'use strict';

exports.allowOnly = function(accessLevel, callback) {
    function checkUserRole(req, res) {
        if(!(accessLevel & req.user.role)) {
            res.sendStatus(403);
            return;
        }

        callback(req, res);
    }

    return checkUserRole;
};

We begin by exporting an allowOnly function that returns another function called checkUserRole.

The allowOnly function accepts an accessLevel and a callback function as params. Both will be provided by us while defining new routes that need to be protected. The callback function will represent the controller method that needs to be called when the route is accessed and the accessLevel the allowed user types to that route.

The returned function will act as a middleware function for our routes as it accepts both the request and response params.

Before calling our callback function we have four lines of code that define our route protection. We check if the request user role — which will be provided by the passport library — has access to this route based on the accessLevel we provided. If not, we respond with 403 - Forbidden. If the user has access, we then call our callback function and pass it the request and response.

We are now finally ready to create our first protected route.

User Protected Route

Inside the routes/api.js file, let’s add a protected route both using the passport library to check the user’s token and our new allowOnly function.

# app/routes/api.js
// ...

router.get('/profile', passport.authenticate('jwt', { session: false }), allowOnly(config.accessLevels.user, UserController.index));

// ...

The code above create a new route called /profile and uses passport‘s authenticate() method to verify the user’s token. Finally, we use our allowOnly function in which we provide the user access level and the index method of a UserController.

This won’t work yet as we haven’t included the allowOnly function in our file or the non-existent UserController. Let’s do that first.

Inside our app/controllers folder, we create a new file called userController.js and inside we define the UserController and the index method.

# app/controllers/userController.js
'use strict';

// The user controller.
var UserController = {
    index: function(req, res) {
        res.status(200).json({ message: 'Welcome to the users area ' + req.user.username + '!' });
    }
};

module.exports = UserController;

The code above is not doing more than responding successfully when accessed and returning a friendly welcome message for our — hopefully — registered user.

Finally, we can finish off by including the controller and the allowOnly function inside our API’s routes.

# /routes/api.js
'use strict';

var router = require('express').Router();

var config = require('../config'),
    AuthController = require('../controllers/authController'),
    allowOnly = require('../services/routesHelper').allowOnly,
    UserController = require('../controllers/userController');

// ...

Let’s restart the server and test our user protected route.

If I attempt to access it directly I get an Unauthorized error, this was expected.

Using a token, however, we can access the user protected route. That’s great!

Admin Protected Route

We can now create a similar route now for admin users. All we have to do is specify a different access level in the allowOnly function, like so:

// ...

router.get('/admin', passport.authenticate('jwt', { session: false }), allowOnly(config.accessLevels.admin, AdminController.index));

//...

Now we can finish off by creating the AdminController and the index route.

# controllers/adminController.js
'use strict';

// The admin controller.
var AdminController = {
    index: function(req, res) {
        res.status(200).json({ message: 'Welcome to the admin area ' + req.user.username + '!' });
    }
};

module.exports = AdminController;

Looks complete to me, except for not having an admin user yet. We can register a new one and change the user role in the database manually.

Logging in with the new admin user and attempting to access the /api/admin route should now work.

Excellent! Now we can test and make sure the regular users can’t access the admin only route using a regular user token.

As expected the server returns 403 - Forbidden.

Conclusion

Congrats! We managed to finish the remaining backend functionality and now have a working RESTful API that supports user registration, authentication and prevents route access based on user role. We are now ready to jump into building our application’s frontend using Angular.

Don’t be shy! Share your thoughts about this post or ask for help if you’re stuck in the comments below. I’ll be glad to assist.

Next Up:

Creating a Role-Based User Authentication System with Angular, Express and MySQL — Part 3: The Front-end

Sharing is Caring

If you 💖 this post, I’d appreciate if you’d let your friends know about it too. You can use the floating buttons to share this post on various social networks. Thanks! 🙂

  • SteppenWolf

    Hi Catalin!

    First of all, thanks a lot for this awesome tutorial! Its helping me a lot in a project that im working on before ending my grade. But im having a issue.
    When setting up that routes are protected, when i send a get request without token in Postman, i get the expected result with an Unauthorized response. But, when i try, as in your example, to send a Authorization header with the signin user token, i still get an Unauthorized response (?). I checked all the the methods on api and auth controller. What could be the problem?

    Thanks

    • Hi! I’m glad you found the tutorial useful. Can you post your code somewhere so I could take a look, please? 🙂 Cheers!

      • SteppenWolf

        Hi Catalin! Finally I resolved the issue. My project was a exact copy of yours. The problem was in that line of the authController.js:

        res.json({
        success: true,
        token: ‘JWT ‘ + token,
        role: user.role
        });

        I missed the last update on your project in github where token: ‘JWT’ + token, should be ‘JWT ‘ and not ‘JWT’.

        The other problem was to change the ‘username’ in passportStrategy.js in that way:

        passport.use(new JWTStrategy(options, function(JWTPayload, callback) {
        User.findOne({ where: { email: JWTPayload.email } }) <– There was my other problem! I changed 'JWTPayload.username' by 'JWTPayload.email'
        .then(function(user) {
        if(!user) {
        callback(null, false);
        return;
        }

        callback(null, user);
        })
        }));

        With these changes I been able to get my response 'Welcome to user area (email) ' correctly.

        Anyways, thanks for your quick response. There any advice that you could give when scalating the project?

        Thanks again

  • Rama Astadipati

    Wow thank you so much for writing the very comprehensive tutorials, I think you need to make project on Udemy or other some place then posting here, then i’ll be your students as well.
    Thank you so much @hiskie:disqus

  • You forgot to import ‘controllers/adminController.js’ (file ‘routes/api.js’)