Authentication is an integral part of any Web application. In this tutorial, we will discuss token-based authentication systems and how they differ from traditional login systems. At the end of this tutorial, you’ll see a complete application built using AngularJS and NodeJS.

Traditional authentication systems

Before we start talking about token-based authentication systems, let’s look at traditional authentication systems.

  1. The user enters the user name and password in the login field, and then clicks Login.

  2. After the request is sent, the user is validated through a back-end query database. If the request is valid, a session is created using the information obtained in the database, and the session information is returned in the response header. The purpose is to store the session ID in the browser.
  3. Provide this session information when accessing restricted back-end servers in your application;
  4. If the session information is valid, the user is allowed to access the restricted backend server and the rendered HTML content is returned.

width=”600″>


Everything was great up until now. The Web application works, and it can authenticate user information and then access restricted back-end servers; But what happens when you develop other terminals, such as Android apps? Can you still use your current app to authenticate mobile and distribute restricted content? The truth is, no. There are two main reasons:

  1. Sessions and cookies don’t work on mobile applications. You cannot share the session and cookie created by the server with the mobile terminal.

  2. In this application, the rendered HTML is returned. But on the mobile side, you need to include something like JSON or XML in the response.

In this case, a standalone client service is required.

Token-based authentication

In token-based authentication, cookies and sessions are no longer used. Tokens can be used to authenticate users each time they make a request to the server. We redesigned the idea using token-based authentication.

The following control flow will be used:

  1. The user enters the user name and password in the login form, and then clicks Login.

  2. After the request is sent, the user is validated through a back-end query database. If the request is valid, create a token using the information obtained in the database, and then return this information in the response header. The purpose is to store the token in the browser’s local storage.
  3. Provide token information every time you send a request to access a restricted back-end server in your application;
  4. If the token from the request header is valid, the user is allowed to access the restricted backend server and return JSON or XML.

In this example, we don’t return session or cookie, and we don’t return any HTML content. That means we can apply this architecture to all clients for a particular application. You can take a look at the following architecture:

width=”600″>

So, what is JWT here?

JWT

JWT stands for JSON Web Token, which is a Token format used to authenticate headers. This token allows you to transfer information between two systems in a secure way. For educational purposes, we’ll consider JWT as an “anonymous token.” An anonymous token contains three parts: header, payload, and signature.

  • The header is the part of the token and stores the type and encoding of the token. Base-64 encoding is usually used.

  • Payload contains information. You can store any kind of information, such as user information, product information, etc. They are stored using base-64 encoding.
  • Signature contains a mixture of header, payload, and key. The key must be securely stored on the server.

You can see JWT just going with an instance token below:

width=”600″>

You don’t have to worry about implementing the anonymous Token generator function, as it is already implemented in multiple versions for many commonly used languages. Here are a few:

NodeJS:
Auth0 / node – jsonwebtoken dead simple

PHP: firebase/PHP – JWT, dead simple

Java: auth0 / Java – JWT, dead simple

Ruby: progrium/Ruby – JWT, dead simple

The.net: AzureAD/azure – activedirectory – identitymodel – extensions – for – dotnet, dead simple

Python: progrium/pyjwt dead simple

An instance

After discussing some of the basics of token-based authentication, let’s look at an example. Take a look at the following points, and then we will analyze it in detail:

width=”600″>


advantage

Token-based authentication has several advantages in solving thorny problems:

  • The Client Independent Services. In token-based authentication, tokens are transmitted through the request header rather than storing authentication information in a session or cookie. That means stateless. You can send requests to the server from any terminal that can send HTTP requests.
  • The CDN. In most modern applications, the View renders in the back and the HTML content is returned to the browser. Front-end logic depends on back-end code. This dependence is really unnecessary. Moreover, several problems arise. For example, if you work with a design agency and the designer helps you with the FRONT-END HTML, CSS, and JavaScript, you need to take the front-end code and transplant it to your back-end code, for rendering purposes of course. After a few changes, the HTML you render may look very different from the code the designer finished. In token-based authentication, you can develop front-end projects that are completely independent of the back-end code. The back-end code will return a JSON instead of rendering HTML, and you can put the minimal, compressed code on the CDN. When you visit a Web page, the HTML content is served by the CDN, and the page content is populated by an API service using tokens in the authentication header.
  • No cookie-session (or No CSRF). CSRF is a pain point in modern Web security because it does not check whether a request source is trustworthy. To solve this problem, a token pool is used to send the relevant tokens on each form request. In token-based authentication, there is already a token applied to the authentication header, and the CSRF does not contain that information.
  • Persistent Token Store. When session reads, writes, or deletes are performed in an application, a file operation takes place in the temp folder of the operating system, at least for the first time. Assume there are multiple servers and a session is created on the first service. When you send a request again and the request lands on another server, the session information does not exist and an “unauthenticated” response is received. I know that you can solve this problem with a sticky session. However, in token-based authentication, this problem is solved naturally. There is no sticky session problem because the token of that request is intercepted on every request sent to the server.

These are the most obvious advantages of token-based authentication and communication. So much for the theory and architecture of token-based authentication. Here’s an example.

Examples of application

You’ll see two applications for demonstrating token-based authentication:

  1. token-based-auth-backend
  2. token-based-auth-frontend

In the back-end project, including the service interface, the JSON format returned by the service. The service layer does not return views. In a front-end project, AngularJS sends requests to the back-end service.

token-based-auth-backend

In the back-end project, there are three main files:

  • Package. json is used to manage dependencies;

  • Models \ user.js contains User models that might be used to handle database operations about users;
  • Server.js is used for project boot and request processing.

That’s it! The project is so simple that you don’t have to go deep to understand the main concepts.

{" name ":" presents - a restful - auth ", "version" : "0.0.1", "dependencies" : {" express ":" 4 x ", "body - parser" : "~ 1.0.0", "Morgan" : "latest", "mongoose" : "3.8.8," "jsonwebtoken" : "0.4.0"}, "engines" : {" node ":" > = 0.10.0 "}}Copy the code

Package. json contains the dependencies for this project: Express for MVC, Body-Parser for simulating POST request operations in NodeJS, Morgan for requesting logins, Mongoose for connecting MongoDB for our ORM framework, Finally, jsonWebToken is used to create JWT using our User model. If the project is created with NodeJS of version >= 0.10.0, there is also a property called Engines. This is useful for PaaS services like HeroKu. We’ll cover that topic in another section as well.

var mongoose     = require('mongoose');
var Schema       = mongoose.Scema;

var UserSchema   = new Schema({
    email: String,
    password: String,
    token: String
});

module.exports = mongoose.model('User', UserSchema);
Copy the code

As mentioned above, we can generate a token by using the user’s payload model. This model helps us process user requests on MongoDB. In user.js, user-schema is defined and User models are created using the Mogoose model. This model provides database operations.

Our dependency and user models are defined, and we now envision those as a service to handle a particular request.

// Required Modules
var express    = require("express");
var morgan     = require("morgan");
var bodyParser = require("body-parser");
var jwt        = require("jsonwebtoken");
var mongoose   = require("mongoose");
var app        = express();
Copy the code

In NodeJS, you can include a module in your project using require. As a first step, we need to introduce the necessary modules into the project:

var port = process.env.PORT || 3001;
var User     = require('./models/User');
// Connect to DB
mongoose.connect(process.env.MONGO_URL);
Copy the code

The service layer provides services through a specified port. If you don’t specify a port in the environment variable, you can use that, or port 3001 as we defined. Then, the User model is included, and database connections are established to handle some User operations. Don’t forget to define a MONGO_URL environment variable for the database connection URL.

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(morgan("dev"));
app.use(function(req, res, next) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
    next();
});
Copy the code

In the previous section, we did some configuration to simulate an HTTP request using Express in NodeJS. We allow requests from different domains in order to set up a separate client system. If you fail to do so, you may trigger a browser CORS (Cross-domain request sharing) error.

  • Access-control-allow-origin allows all domain names.

  • You can send POST and GET requests to the device.
  • Allow X-requested-with and Content-Type headers.
app.post('/authenticate', function(req, res) { User.findOne({email: req.body.email, password: req.body.password}, function(err, user) { if (err) { res.json({ type: false, data: "Error occured: " + err }); } else { if (user) { res.json({ type: true, data: user, token: user.token }); } else { res.json({ type: false, data: "Incorrect email/password" }); }}}); });Copy the code

We’ve introduced all the required modules and defined the configuration file, so it’s time to define the request handler function. In the code above, you will get a JWT when you send a POST request to /authenticate with the username and password provided. First, query the database by username and password. If the user exists, the user’s data will be returned along with its token. But what if there is no user name or the password is incorrect?

app.post('/signin', function(req, res) { User.findOne({email: req.body.email, password: req.body.password}, function(err, user) { if (err) { res.json({ type: false, data: "Error occured: " + err }); } else { if (user) { res.json({ type: false, data: "User already exists!" }); } else { var userModel = new User(); userModel.email = req.body.email; userModel.password = req.body.password; userModel.save(function(err, user) { user.token = jwt.sign(user, process.env.JWT_SECRET); user.save(function(err, user1) { res.json({ type: true, data: user1, token: user1.token }); }); }}}})); });Copy the code

When you send a POST request to /signin using the username and password, a new user is created from the requested user information. In line 19, you can see that a new JSON is generated by the JSONWebToken module and then assigned to the JWT variable. The certification part has been completed. What if we access a restricted back-end server? How do we access that back-end server?

app.get('/me', ensureAuthorized, function(req, res) { User.findOne({token: req.token}, function(err, user) { if (err) { res.json({ type: false, data: "Error occured: " + err }); } else { res.json({ type: true, data: user }); }}); });Copy the code

When you send a GET request to /me, you will GET information about the current user, but in order to continue the request to the back-end server, the ensureAuthorized function will be executed.

function ensureAuthorized(req, res, next) {
    var bearerToken;
    var bearerHeader = req.headers["authorization"];
    if (typeof bearerHeader !== 'undefined') {
        var bearer = bearerHeader.split(" ");
        bearerToken = bearer[1];
        req.token = bearerToken;
        next();
    } else {
        res.send(403);
    }
}
Copy the code

In this function, the request header is intercepted and the authorization header is extracted. If there is an unnamed token in the header, the request continues by calling the next() function. If the token does not exist, you will get a 403 (Forbidden) return. We go back to the /me event handler and use req.token to get the user data corresponding to this token. When you create a new user, a token is generated and stored in the user model in the database. Those tokens are unique.

This simple example already has three event handlers. Then, you will see;

process.on('uncaughtException', function(err) {
    console.log(err);
});
Copy the code

NodeJS applications can crash when the program fails. Adding the above code will save it and an error log will hit the console. Finally, we can start the service using the following code snippet.

// Start Server
app.listen(port, function () {
    console.log( "Express server listening on port " + port);
});
Copy the code

To sum up:

  • The introduction of the module

  • Configured correctly
  • Define the request handler function
  • Define the middleware used to intercept restricted end point data
  • Start the service

We have completed the back-end services. Now that the application can be used by multiple terminals, you can deploy this simple application to your server or Heroku. There is a file called Procfile in the root directory of the project. Now deploy the service to Heroku.

Heroku deployment

You can download the project’s back-end code from the GitHub library.

I’m not going to teach you how to create an application in Heroku; If you haven’t done this yet, check out this article. After creating the Heroku app, you can add an address to your project using the following command:

git remote add heroku 
Copy the code

You have now cloned the project and added the address. After git add and git commit, you can use git push heroku master to push your code to Heroku. When you successfully push the project to the repository, Heroku will automatically run the NPM install command to download the dependency files into Heroku’s Temp folder. It then launches your application so you can access the service using HTTP.

token-based-auth-frontend

In the front-end project, AngularJS will be used. I’m only going to cover the main content of the front-end project here, because AngularJS is not covered in this tutorial.

You can download the source code from the GitHub library. In this project, you will look at the following file structure:

width=”600″>

Ngstorage. js is an AngularJS library for manipulating local storage. In addition, there is a global layout file named index.html and sections in the Partials folder that extend the global Layout. Controllers. Js is used to define our controller’s actions at the front end. Services.js is used to send requests to the service we mentioned in the previous project. There is also an app.js file that contains configuration files and module imports. Finally, client.js is used to serve static HTML files (or just index.html, in this example); It can serve static HTML files when you’re not using Apache or any other Web server.

. SRC = "/ / cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js" > SRC = "/ / maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js" > SRC = "/ / cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js" > SRC = "/ / cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-route.min.js" > SRC = "/ lib/ngStorage. Js" > src="/lib/loading-bar.js"> src="/scripts/app.js"> src="/scripts/controllers.js"> src="/scripts/services.js">Copy the code

In the global Layout file, all AngularJS JavaScript files are included, including custom controllers, services, and application files.

'use strict'; /* Controllers */ angular.module('angularRestfulAuth') .controller('HomeCtrl', ['$rootScope', '$scope', '$location', '$localStorage', 'Main', function($rootScope, $scope, $location, $localStorage, Main) { $scope.signin = function() { var formData = { email: $scope.email, password: $scope.password } Main.signin(formData, function(res) { if (res.type == false) { alert(res.data) } else { $localStorage.token = res.data.token;  window.location = "/"; } }, function() { $rootScope.error = 'Failed to signin'; }) };  $scope.signup = function() { var formData = { email: $scope.email, password: $scope.password } Main.save(formData, function(res) { if (res.type == false) { alert(res.data) } else { $localStorage.token = res.data.token;  window.location = "/" } }, function() { $rootScope.error = 'Failed to signup'; }) };  $scope.me = function() { Main.me(function(res) { $scope.myDetails = res;  }, function() { $rootScope.error = 'Failed to fetch details'; }) };  $scope.logout = function() { Main.logout(function() { window.location = "/" }, function() { alert("Failed to logout! "); }); }; $scope.token = $localStorage.token; }])Copy the code

In the above code, the HomeCtrl controller is defined and some required modules are injected (such as $rootScope and $scope). Dependency injection is one of the most powerful properties of AngularJS. $scope is an AngularJS intermediate variable between a controller and a view, which means you can use test in a view if you define $scope.test=…. in a specific controller .

In the controller, some utility functions are defined, such as:

  • Signin initializes a login button in the login form;

  • Signup is used to handle registration operations;
  • Me can have a ME button in layout.

In the global Layout and main menu table, you can see the data-ng-Controller property, whose value is HomeCtrl. That means that the DOM elements of this menu can share scope with HomeCtrl. When you click the sign-up button in the form, the sign-up function in the controller file will be executed, and in this function, the login service is from the Main service that has been injected into the controller.

The main structure is View -> Controller -> Service. The service sends a simple Ajax request to the back end to get the specified data.

'use strict'; angular.module('angularRestfulAuth') .factory('Main', ['$http', '$localStorage', function($http, $localStorage){ var baseUrl = "your_service_url"; function changeUser(user) { angular.extend(currentUser, user);  } function urlBase64Decode(str) { var output = str.replace('-', '+').replace('_', '/');  switch (output.length % 4) { case 0: break; case 2: output += '=='; break; case 3: output += '='; break; default: throw 'Illegal base64url string! '; } return window.atob(output); } function getUserFromToken() { var token = $localStorage.token; var user = {};  if (typeof token ! == 'undefined') { var encoded = token.split('.')[1]; user = JSON.parse(urlBase64Decode(encoded)); } return user;  } var currentUser = getUserFromToken(); return { save: function(data, success, error) { $http.post(baseUrl + '/signin', data).success(success).error(error) }, signin: function(data, success, error) { $http.post(baseUrl + '/authenticate', data).success(success).error(error) }, me: function(success, error) { $http.get(baseUrl + '/me').success(success).error(error) }, logout: function(success) { changeUser({}); delete $localStorage.token; success(); } }; } ]);Copy the code

In the code above, you will see that the service function requests authentication. In controller.js, you may have seen functions like http://Main.me. Here the Main service is injected into the controller, and inside it, other services belonging to the service are invoked directly.

These functions simply send Ajax requests to our deployed server cluster. Don’t forget to put the URL of the service into baseUrl in the code above. When you put the service deployment to Heroku, you will get a similar service URL of http://appname.herokuapp.com. In the code above, you should set the var baseUrl = “http://appname.herokuapp.com”.

In the registration or login part of the application, the anonymous token responds to the request and the token is stored in local storage. When you request a service from the back end, you need to put this token in the header. You can do this using AngularJS interceptors.

$httpProvider.interceptors.push(['$q', '$location', '$localStorage', function($q, $location, $localStorage) { return { 'request': function (config) { config.headers = config.headers || {};  if ($localStorage.token) { config.headers.Authorization = 'Bearer ' + $localStorage.token; } return config;  }, 'responseError': function(response) { if(response.status === 401 || response.status === 403) { $location.path('/signin');  } return $q.reject(response); } }; }]);Copy the code

In the above code, each request is intercepted and the authentication header and value are placed in the header.

In front end projects, there are incomplete pages such as Signin, Signup, Profile Details, and VB. These pages are associated with a specific controller. You can see it in app.js:

angular.module('angularRestfulAuth', [
    'ngStorage',
    'ngRoute'
])
.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {

    $routeProvider.
        when('/', {
            templateUrl: 'partials/home.html',
            controller: 'HomeCtrl'
        }).
        when('/signin', {
            templateUrl: 'partials/signin.html',
            controller: 'HomeCtrl'
        }).
        when('/signup', {
            templateUrl: 'partials/signup.html',
            controller: 'HomeCtrl'
        }).
        when('/me', {
            templateUrl: 'partials/me.html',
            controller: 'HomeCtrl'
        }).
        otherwise({
            redirectTo: '/'
        });
Copy the code

As shown in the code above, when you visit /, home.html will be rendered. Here’s another example: if you visit /signup, signup.html will be rendered. Rendering is done in the browser, not on the server.

conclusion

You can see how the project we discuss in this tutorial works by checking out this example.

Token-based authentication systems help you set up an authentication/authorization system when you are developing client-independent services. With this technique, you only have to focus on the service (or API).

The authentication/authorization part will be handled by token-based authentication systems as the front layer of your service. You can access and use any service like a Web browser, Android, iOS or a desktop client.