Learn About API Authorization In Node.js

How it Works

Let’s take a quick look at how the work is going to be done.

  1. Generate a customized token by each HTTP request.
  2. Pass it through request header (x-access-token)
  3. Server extracts the token from each request
  4. Verify the custom token by regenerating in server
  5. If token matches,  then system checks the user permissions from the database
  6. Otherwise, the system will respond with a status code of 403/404

Prerequisites

It’s highly recommended that you review these previous posts.

Let’s get started

Previously we have created User table in the database; now, we need to create another table named ”UserAuthorization/Authorization”.

  1. CREATE TABLE [dbo].[Authorization](  
  2.     [Id] [intNOT NULL,  
  3.     [UserId] [nvarchar](50) NULL,  
  4.     [CanView] [bitNULL,  
  5.     [CanPost] [bitNULL,  
  6.     [CanPut] [bitNULL,  
  7.     [CanDelete] [bitNULL,  
  8.  CONSTRAINT [PK_Authorization] PRIMARY KEY CLUSTERED   
  9. (  
  10.     [Id] ASC  
  11. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ONON [PRIMARY]  
  12. ON [PRIMARY]  
  13. GO  

Add a new Stored Procedure to validate the user action.

  1. CREATE PROCEDURE [dbo].[UserAuthorization]  
  2.     @Userid NVarchar(250),  
  3.     @Methodtype NVarchar(250)  
  4. AS  
  5. BEGIN  
  6.     -- SET NOCOUNT ON added to prevent extra result sets from  
  7.     -- interfering with SELECT statements.  
  8.     SET NOCOUNT ON;  
  9.   
  10.     -- Insert statements for procedure here  
  11.     SELECT CASE WHEN result = 1 THEN 'true' ELSE 'false' END AS result FROM  
  12.     (  
  13.         SELECT CASE  
  14.            WHEN @Methodtype = 'GET' THEN [CanView]  
  15.            WHEN @Methodtype = 'POST' THEN [CanPost]  
  16.            WHEN @Methodtype = 'PUT' THEN [CanPut]  
  17.            WHEN @Methodtype = 'DELETE' THEN [CanDelete]  
  18.            ELSE 0  
  19.            END AS result  
  20.   
  21.         FROM [dbo].[AuthorizationWHERE [UserId] = @Userid  
  22.     )AUTH    
  23. END  
  24.   
  25. --EXEC UserAuthorization 'Shashangka', 'GET'  
  26. GO  

We are done with the database work. We are going to start from our previous sample of the application. Download it from GitHub, then open the application using Visual Studio 2017. In our application, we need to modify both client-side and server-side. First, we are going to modify in client-side.

Client-Side

Let’s get started with the client-side first. Here we are going to generate the token based on several hash processes with HMAC SHA256 & Base64 encoding.

Here, HMAC is for Hash Message Authentication Code & SHA256 is the hash function.

Get more: https://en.wikipedia.org/wiki/HMAC

Token Generation

We need to add these two libraries to our layout page to enable the hash/encoding process.

  1. <script src="/javascripts/hmac-sha256.js"></script>  
  2. <script src="/javascripts/enc-base64-min.js"></script>  

The below function is generating the token in client-side which is passed through each HTTP request header.

  1. function generateSecurityToken(actionType, loggedUser) {  
  2.     var model = {  
  3.         username: loggedUser,  
  4.         key: actionType,  
  5.         userAgent: navigator.userAgent.replace(/ \.NET.+;/, '')  
  6.     };  
  7.   
  8.     var message = [model.username, model.userAgent].join(':');  
  9.     var hash = CryptoJS.HmacSHA256(message, model.key);  
  10.     var token = CryptoJS.enc.Base64.stringify(hash);  
  11.     var tokenId = [model.username, model.key].join(':');  
  12.     var tokenGenerated = CryptoJS.enc.Utf8.parse([token, tokenId].join(':'));  
  13.   
  14.     return CryptoJS.enc.Base64.stringify(tokenGenerated);  
  15. };  

Create a .js file in public/javascripts folder with the name of “authorization.js”, copy the code snippet then paste it.

Code Explanation

From token generation function, we can see the hash is generated by cryptographic hash function

var hash = CryptoJS.HmacSHA256(message, model.key);

Here, the first argument is the message to hash & the second one is the secret key to authenticate the message.

Get more: https://code.google.com/archive/p/crypto-js

Finally, the token is encoded by Base64 encoding type.

Layout.html

Now call the “authorization.js” file to layout page like below.

  1. <!--Token Generation-->  
  2. <script src="/javascripts/hmac-sha256.js"></script>  
  3. <script src="/javascripts/enc-base64-min.js"></script>  
  4. <script src="/javascripts/authorization.js"></script>  

AngularJS Controller

Let’s modify our existing “UserController” to add the token in request header with each HTTP method.

For GET Method

headers: { 'x-access-token': generateSecurityToken('GET', $scope.loggedUser)}

For POST Method

headers: { 'x-access-token': generateSecurityToken('POST', $scope.loggedUser)}

For PUT Method

headers: { 'x-access-token': generateSecurityToken('PUT', $scope.loggedUser)}

For DELETE Method

headers: { 'x-access-token': generateSecurityToken('DELETE', $scope.loggedUser) }

Finally, the UserController

  1. templatingApp.controller('UserController', ['$scope''$http'function ($scope, $http) {  
  2.     $scope.title = "All User";  
  3.     $scope.loggedUser = 'Shashangka';//Change Username according to the database  
  4.     $scope.ListUser = null;  
  5.     $scope.userModel = {};  
  6.     $scope.userModel.Id = 0;  
  7.   
  8.     getallData();  
  9.   
  10.     //******=========Get All User=========******  
  11.     function getallData() {  
  12.         $http({  
  13.             method: 'GET',  
  14.             url: '/api/user/getAll/',  
  15.             headers: { 'x-access-token': generateSecurityToken('GET', $scope.loggedUser) }  
  16.         }).then(function (response) {  
  17.             $scope.ListUser = response.data;  
  18.         }, function (error) {  
  19.             showNotif(error.data.message);  
  20.             console.log(error);  
  21.         });  
  22.     };  
  23.   
  24.     //******=========Get Single User=========******  
  25.     $scope.getUser = function (user) {  
  26.         $http({  
  27.             method: 'GET',  
  28.             url: '/api/user/getUser/' + parseInt(user.Id),  
  29.             headers: { 'x-access-token': generateSecurityToken('GET', $scope.loggedUser) }  
  30.         }).then(function (response) {  
  31.             $scope.userModel = response.data[0];  
  32.             //console.log($scope.userModel);  
  33.         }, function (error) {  
  34.             showNotif(error.data.message);  
  35.             console.log(error);  
  36.         });  
  37.     };  
  38.   
  39.     //******=========Save User=========******  
  40.     $scope.saveUser = function () {  
  41.         $http({  
  42.             method: 'POST',  
  43.             url: '/api/user/setUser/',  
  44.             data: $scope.userModel,  
  45.             headers: { 'x-access-token': generateSecurityToken('POST', $scope.loggedUser) }  
  46.         }).then(function (response) {  
  47.             showNotif("Data Saved")  
  48.             $scope.reset();  
  49.             getallData();  
  50.         }, function (error) {  
  51.             showNotif(error.data.message);  
  52.             console.log(error);  
  53.         });  
  54.     };  
  55.   
  56.     //******=========Update User=========******  
  57.     $scope.updateUser = function () {  
  58.         $http({  
  59.             method: 'PUT',  
  60.             url: '/api/user/putUser/',  
  61.             data: $scope.userModel,  
  62.             headers: { 'x-access-token': generateSecurityToken('PUT', $scope.loggedUser) }  
  63.         }).then(function (response) {  
  64.             showNotif("Data Updated")  
  65.             $scope.reset();  
  66.             getallData();  
  67.         }, function (error) {  
  68.             showNotif(error.data.message);  
  69.             console.log(error);  
  70.         });  
  71.     };  
  72.   
  73.     //******=========Delete User=========******  
  74.     $scope.deleteUser = function (user) {  
  75.         var IsConf = confirm('You are about to delete ' + user.Name + '. Are you sure?');  
  76.         if (IsConf) {  
  77.             $http({  
  78.                 method: 'DELETE',  
  79.                 url: '/api/user/deleteUser/' + parseInt(user.Id),  
  80.                 headers: { 'x-access-token': generateSecurityToken('DELETE', $scope.loggedUser) }  
  81.             }).then(function (response) {  
  82.                 showNotif("Data Deleted")  
  83.                 $scope.reset();  
  84.                 getallData();  
  85.             }, function (error) {  
  86.                 showNotif(error.data.message);  
  87.                 console.log(error);  
  88.             });  
  89.         }  
  90.     };  
  91.   
  92.     //******=========Login User=========******  
  93.     $scope.loginUser = function () {  
  94.         $http({  
  95.             method: 'POST',  
  96.             url: '/api/user/login/',  
  97.             data: $scope.userModel  
  98.         }).then(function (response) {  
  99.             $scope.reset();  
  100.         }, function (error) {  
  101.             console.log(error);  
  102.         });  
  103.   
  104.     };  
  105.   
  106.     //******=========Clear Form=========******  
  107.     $scope.reset = function () {  
  108.         var msg = "Form Cleared";  
  109.         $scope.userModel = {};  
  110.         $scope.userModel.Id = 0;  
  111.         showNotif(msg)  
  112.     };  
  113. }]);  

Server-Side

After completing the client-side token generation and sending it with the HTTP request it’s time to work with server-end modifications.

We need to install utf8 npm packages to encode/decode our generated token.

  • utf8 - UTF-8 encoder/decoder for Node.js

To install the package right click on project Go to > Open Command Prompt Here then type “npm install utf8” command, press Enter.

Verify Token

Let’s create a .js file in data folder by naming it “verifyToken.js”. In this process the server extracts the token from each request then performs two step validation

  1. Comparing the custom token by regenerating in the server and
  2. Validating the user permission from database on each request

Code Explanation

Extracting the token from request header

  1. var token = req.headers['x-access-token'];  

Regenerating Token in Server-side to compare

  1. var message = [keymodel.userid, keymodel.useragent].join(':').toString();  
  2. var sec_key = keymodel.actionType;  
  3. var servertoken = crypto.createHmac(encConfig.hType.toString(), sec_key).update(message).digest(encConfig.eType.toString());  

Finally verifyToken.js

  1. var utf8 = require('utf8');  
  2. var crypto = require('crypto');  
  3. var dbService = require('./dbService');  
  4.   
  5. var encConfig = {  
  6.     hType: "sha256",  
  7.     eType: "base64",  
  8.     cEnc: "ascii"  
  9. };  
  10.   
  11. var validateToken = function (req, res, next) {  
  12.     var token = req.headers['x-access-token'];  
  13.     var userAgent = req.headers['user-agent'];  
  14.   
  15.     var key = utf8.encode(new Buffer(token, encConfig.eType.toString()).toString(encConfig.cEnc));  
  16.     const parts = key.split(':');  
  17.     var keymodel = {};  
  18.   
  19.     if (parts.length === 3) {  
  20.         keymodel = {  
  21.             clientToken: parts[0],  
  22.             userid: parts[1],  
  23.             actionType: parts[2],  
  24.             useragent: userAgent  
  25.         };  
  26.     }  
  27.   
  28.     //Generate Token Server-Side  
  29.     var message = [keymodel.userid, keymodel.useragent].join(':').toString();  
  30.     var sec_key = keymodel.actionType;  
  31.     var servertoken = crypto.createHmac(encConfig.hType.toString(), sec_key).update(message).digest(encConfig.eType.toString());  
  32.   
  33.     //Validate Token  
  34.     if (keymodel.clientToken == servertoken) {  
  35.         var query = "[UserAuthorization] '" + keymodel.userid + "', '" + keymodel.actionType + "'";  
  36.         dbService.executeQuery(query, function (data, err) {  
  37.             if (err) {  
  38.                 throw err;  
  39.             } else {  
  40.                 var response = data.recordset[0].result;  
  41.                 if (response == 'true') {  
  42.                     next();  
  43.                 }  
  44.                 else {  
  45.                     return res.status(403).send({ message: 'Authorization Failed!! You are not Permitted!!' });  
  46.                 }  
  47.             }  
  48.         });  
  49.     }  
  50.     else {  
  51.         return res.status(404).send({ message: 'Invalid Token!!' });  
  52.     }  
  53. }  
  54.   
  55. module.exports = validateToken;  

Secure API’s

Let’s include verifyToken module.

  1. let verifyToken = require('../verifyToken');  

Now we need to add verifyToken middleware as a second argument in express route to verify token.

  1. //GET API  
  2. router.get("/api/user/getAll", verifyToken, function (req, res)  
  3. //GET API  
  4. router.get("/api/user/getUser/:id", verifyToken, function (req, res)  
  5. //POST API  
  6. router.post("/api/user/setUser", verifyToken, function (req, res)  
  7. //PUT API  
  8. router.put("/api/user/putUser", verifyToken, function (req, res)  
  9. //DELETE API  
  10. router.delete("/api/user/deleteUser/:id", verifyToken, function (req, res)  

Get More: https://expressjs.com/en/guide/using-middleware.html

API’s

  1. var express = require('express');  
  2. var router = express.Router();  
  3. var dbService = require('../dbService');  
  4. let verifyToken = require('../verifyToken');  
  5.   
  6. //GET API  
  7. router.get("/api/user/getAll", verifyToken, function (req, res) {  
  8.     var query = "GetUsers";  
  9.     dbService.executeQuery(query, function (data, err) {  
  10.         if (err) {  
  11.             throw err;  
  12.         } else {  
  13.             res.send(data.recordset);  
  14.         }  
  15.         res.end();  
  16.     });  
  17. });  
  18.   
  19. // GET API  
  20. router.get("/api/user/getUser/:id", verifyToken, function (req, res) {  
  21.     var query = "[GetUserById] " + parseInt(req.params.id) + "";  
  22.   
  23.     dbService.executeQuery(query, function (data, err) {  
  24.         if (err) {  
  25.             throw err;  
  26.         } else {  
  27.             res.send(data.recordset);  
  28.         }  
  29.         res.end();  
  30.     });  
  31. });  
  32.   
  33. //POST API  
  34. router.post("/api/user/setUser", verifyToken, function (req, res) {  
  35.     var query = "[SetUser] '" + req.body.Name + "', '" + req.body.Email + "', '" + req.body.Phone + "'";  
  36.     dbService.executeQuery(query, function (data, err) {  
  37.         if (err) {  
  38.             throw err;  
  39.         } else {  
  40.             res.send(data.recordset);  
  41.         }  
  42.         res.end();  
  43.     });  
  44. });  
  45.   
  46. //PUT API  
  47. router.put("/api/user/putUser", verifyToken, function (req, res) {  
  48.     var query = "[PutUser] " + parseInt(req.body.Id) + ", '" + req.body.Name + "','" + req.body.Email + "', '" + req.body.Phone + "'";  
  49.     dbService.executeQuery(query, function (data, err) {  
  50.         if (err) {  
  51.             throw err;  
  52.         } else {  
  53.             res.send(data.recordset);  
  54.         }  
  55.         res.end();  
  56.     });  
  57. });  
  58.   
  59. // DELETE API  
  60. router.delete("/api/user/deleteUser/:id", verifyToken, function (req, res) {  
  61.     var query = "[DeleteUser] " + parseInt(req.params.id) + "";  
  62.   
  63.     dbService.executeQuery(query, function (data, err) {  
  64.         if (err) {  
  65.             throw err;  
  66.         } else {  
  67.             res.send(data.recordset);  
  68.         }  
  69.         res.end();  
  70.     });  
  71. });  
  72.   
  73. module.exports = router;  

Publishing the App

Go to task explorer in Visual Studio like in the below image,

Node.js

Run the task, this will copy all our application files to the published folder.

Node.js

Open command prompt here (Shift + Right Mouse) then type “nodemon”. We are starting our application using nodemon. If we have any change in our application, nodemon will automatically restart the application.

Node.js

Now open the browser and type the URL: http://localhost:3000

OutPut

In the below table view as we can see the access configuration is set by a particular user.  Let’s test our application according to this configuration.

Node.js

Delete Action

Here we can see the request method “DELETE” is forbidden with the status code of 403.

Node.js

Also, the custom response message is displayed in response tab.

Node.js

Update Action

Here we can see the request method “PUT” is executed successfully with the status code of 200.

Node.js

Source Code

I’ve uploaded the full source code to download/clone @github, Hope this will help

References

  1. https://expressjs.com/en/guide/using-middleware.html
  2. https://code.google.com/archive/p/crypto-js
  3. https://en.wikipedia.org/wiki/HMAC
  4. https://www.codeproject.com/Articles/1110226/Authorization-Filters-in-ASP-NET-Web-API-Angular