Introduction
It is important to restrict unauthorized access to particular
operations/actions of an application. I did an experiment while I was working on a
project that needed to restrict an unauthorized person from performing Crud operations.
The authorization is based on the user role.
OK, Let's get started. Here are the steps; I hope you will enjoy it.
Contents
- SQL Database
- Create a new Database
- Run the DB-script
- ASP.NET MVC Application(Web Api)
- MVC, WebAPI Project
- Install AngularJS
- Authentication &
- Authorization
Create New Database
- /****** Object: Database [ApiSecurity] Script Date: 7/3/2016 11:50:11 AM ******/
- CREATE DATABASE [ApiSecurity] ON PRIMARY
- ( NAME = N'ApiSecurity', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\ApiSecurity.mdf' , SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
- LOG ON
- ( NAME = N'ApiSecurity_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\ApiSecurity_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
- GO
After creating the database, let's download and run the script. Let's create a new MVC Application
MVC Application
Install AngularJS for client side scripting from NuGet package installer.
First, we need to login for the authentication check
Authentication & Authorization - Authentication : Identity of the user.
- Authorization : Allowed to perform an action.
After successful login(Authentication), we can access the get customer link to show all the customers, only if we have the read permission in the database, given below:
.
In our database table, we have to restrict the access (CanRead to "False") of Administrator to view the customer list.
The result will show a 401 response message while fetching the data from the database, where the logged in user role is an administrator.
Using the code
Here's our API Controller, that is restricted by using [BasicAuthorization] attribute at the top of the Crud methods.
- [RoutePrefix("api/Customer")]
- public class CustomerController : ApiController
- {
- private CustomersMgt objCust = null;
-
-
- [BasicAuthorization, HttpGet, Route("GetCustomers/{pageNumber:int}/{pageSize:int}")]
- public IHttpActionResult GetCustomers(int pageNumber, int pageSize)
- {
- objCust = new CustomersMgt();
- return Json(objCust.GetCustomer(pageNumber, pageSize));
- }
-
-
- [BasicAuthorization, HttpPost, Route("SaveCustomer")]
- public IHttpActionResult SaveCustomer(Customer model)
- {
- objCust = new CustomersMgt();
- return Json(objCust.SaveCustomer(model));
- }
-
-
- [BasicAuthorization, HttpPut, Route("UpdateCustomer")]
- public IHttpActionResult UpdateCustomer(Customer model)
- {
- objCust = new CustomersMgt();
- return Json(objCust.UpdateCustomer(model));
- }
-
-
- [BasicAuthorization, HttpDelete, Route("DeleteCustomer/{CustomerID:int}")]
- public IHttpActionResult DeleteCustomer(int CustomerID)
- {
- objCust = new CustomersMgt();
- return Json(objCust.DeleteCustomer(CustomerID));
- }
- }
The code snippet, given below, of our Customer attribute, which is inherited from AuthorizationFilterAttribute, using System.Web.Http.Filters is targeted to both the class and method. If you want to target only the method, remove the AttributeTargets class-targeted attributes with the or operator.
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
- public class BasicAuthorization : AuthorizationFilterAttribute
- {
- private const string _authorizedToken = "AuthorizedToken";
- private const string _userAgent = "User-Agent";
-
- private UserAuthorizations objAuth = null;
-
- public override void OnAuthorization(HttpActionContext filterContext)
- {
- string authorizedToken = string.Empty;
- string userAgent = string.Empty;
-
- try
- {
- var headerToken = filterContext.Request.Headers.SingleOrDefault(x => x.Key == _authorizedToken);
- if (headerToken.Key != null)
- {
- authorizedToken = Convert.ToString(headerToken.Value.SingleOrDefault());
- userAgent = Convert.ToString(filterContext.Request.Headers.UserAgent);
- if (!IsAuthorize(authorizedToken, userAgent))
- {
- filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
- return;
- }
- }
- else
- {
- filterContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
- return;
- }
- }
- catch (Exception)
- {
- filterContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
- return;
- }
-
- base.OnAuthorization(filterContext);
- }
-
- private bool IsAuthorize(string authorizedToken, string userAgent)
- {
- objAuth = new UserAuthorizations();
- bool result = false;
- try
- {
- result = objAuth.ValidateToken(authorizedToken, userAgent);
- }
- catch (Exception)
- {
- result = false;
- }
- return result;
- }
- }
Here, OnAuthorization is a method, that overrides from the inherited class, and calls when a process requests for the authorization and filterContext is a parameter which encapsulates the information for using System.Web.Http.Filters.AuthorizationFilterAttribute.
In this section, an exception is handled by sending the response of Forbidden (403) & Unauthorized (401) of the response code.
Script for Token Generation Below is the AngularJS script for the token generation at the client end, while each request is a process which generates first and sends it along with the request header to validate.
- AppSecurity.controller('tokenCtrl', ['$scope', '$http', 'crudService', '$sessionStorage',
- function ($scope, $http, crudService, $sessionStorage) {
-
-
- $scope.tokenManager = {
- generateSecurityToken: function (methodtype) {
- var model = {
- username: $sessionStorage.loggeduser,
- key: methodtype,
- ip: $sessionStorage.loggedip,
- userAgent: navigator.userAgent.replace(/ \.NET.+;/, '')
- };
-
- var message = [model.username, model.ip, model.userAgent].join(':');
- var hash = CryptoJS.HmacSHA256(message, model.key);
-
- var token = CryptoJS.enc.Base64.stringify(hash);
- var tokenId = [model.username, model.key].join(':');
- var tokenGenerated = CryptoJS.enc.Utf8.parse([token, tokenId].join(':'));
-
- return CryptoJS.enc.Base64.stringify(tokenGenerated);
- },
- };
- }]);
Token is generated by Base64-encode, where the hash has the message body and the encryption key. In this app, we have used Crud type as key.
Server Token Generation Because of the way in which the client token was generated, we need to re-generate the token. In the same way, we will compare and validate whether the request is fake or not.
- public string generateToken(string userid, string methodtype, string ip, string userAgent)
- {
- string message = string.Join(":", new string[] { userid, ip, userAgent });
- string key = methodtype ?? "";
-
- var encoding = new System.Text.ASCIIEncoding();
-
- byte[] keyByte = encoding.GetBytes(key);
- byte[] messageBytes = encoding.GetBytes(message);
-
- using (var hmacsha256 = new HMACSHA256(keyByte))
- {
- byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
- return Convert.ToBase64String(hashmessage);
- }
- }
Validate Token
This part of the code will compare and validate the request in two steps -- first token is compared and it will validate the authorization from the database to access the Crud operations.
- public bool ValidateToken(string authorizedToken, string userAgent)
- {
- bool result = false;
- try
- {
- string key = Encoding.UTF8.GetString(Convert.FromBase64String(authorizedToken));
- string[] parts = key.Split(new char[] { ':' });
- if (parts.Length == 3)
- {
- objModel = new tokenModel()
- {
- clientToken = parts[0],
- userid = parts[1],
- methodtype = parts[2],
- ip = HostService.GetIP()
- };
-
-
- string serverToken = generateToken(objModel.userid, objModel.methodtype, objModel.ip, userAgent);
- if (objModel.clientToken == serverToken)
- {
- result = ValidateAuthorization(objModel.userid, objModel.methodtype);
- }
- }
- }
- catch (Exception e)
- {
- e.ToString();
- }
- return result;
- }
Authorization
This sample of the code will validate the access permission from the database on each action.
- public bool ValidateAuthorization(string userid, string methodtype)
- {
- bool IsValid = false;
- if (userid != null)
- {
- using (_ctx = new ApiSecurityEntities())
- {
- if (_ctx.UserAuthentications.Any(u => u.LoginID == userid && u.StatusID == 1))
- {
- switch (methodtype)
- {
- case "get":
- IsValid = (from u in _ctx.UserAuthentications
- join r in _ctx.UserRoles on u.RoleID equals r.RoleID
- where u.LoginID == userid && u.StatusID == 1 && r.CanRead == true
- select u).Any();
- break;
- case "post":
- IsValid = (from u in _ctx.UserAuthentications
- join r in _ctx.UserRoles on u.RoleID equals r.RoleID
- where u.LoginID == userid && u.StatusID == 1 && r.CanCreate == true
- select u).Any();
- break;
- case "put":
- IsValid = (from u in _ctx.UserAuthentications
- join r in _ctx.UserRoles on u.RoleID equals r.RoleID
- where u.LoginID == userid && u.StatusID == 1 && r.CanUpdate == true
- select u).Any();
- break;
- case "delete":
- IsValid = (from u in _ctx.UserAuthentications
- join r in _ctx.UserRoles on u.RoleID equals r.RoleID
- where u.LoginID == userid && u.StatusID == 1 && r.CanDelete == true
- select u).Any();
- break;
- default:
- IsValid = false;
- break;
- }
- }
- }
- }
- return IsValid;
- }
I hope this helped.