Introduction
It is important to restrict un-authorized access of a particular operation/action of application. This was experimented while I was working on a project that needed to restrict un-authorized person to perform crud operations. The authorization is based on user role.
OK, let's get started, here are the steps. Hope you will enjoy it.
Contents
- SQL Database
- Create new database
- Run the db-script
- ASP.NET MVC Application (Web API)
- MVC,
WebApi
Project
- Install AngularJS
- Authentication &
- Authorization
Create New Database
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 & run the script. Let's create a new MVC application.
MVC Application
Install the AngularJS for clientside scripting from nuget package installer.
First, we need to login for 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 database.
In our database table, we have restricted the access (CanRead
to "False
") of Administrator to view customer list .
The result will show 401 response message while fetching data from database where logged user role is 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));
}
}
Below code snippet of our custom
Attribute is inherited from AuthorizationFilterAttribute
using System.Web.Http.Filters
. Targeted to both class and method, if you want target only method, then 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 is overridden from inherited class, calls when a process requests for authorization & filterContext
is parameter which encapsulates information for using System.Web.Http.Filters.AuthorizationFilterAttribute
.
In this section, exception is handled by sending response of Forbidden(403) & Unauthorized(401) of response code.
Script for Token Generation
Below is angularJS script for token generation at client end, while each request is processed, this is generated first and sent along with 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);
},
};
}]);
The token is generated by Base64-encode, where the hash has the message body & the encryption key, in this app, we have used crud type as key.
Server Token Generation
The way which the client token was generated, we need to re-generate the token according to the same way, which will compare & validate whether the request is fake?
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 code will compare & validate the request in two steps, first token is compared and it will validate the authorization from database of accessing 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 code will validate the access permission from 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;
}
Hope this will help!