Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Authorization Filters in ASP.NET Web API 2 & AngularJS

0.00/5 (No votes)
4 Jul 2016 1  
In this article, we are going to explore the security issue which will help us to prevent unauthorized access of web API access using custom Authorization Filter.

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

/****** 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 & 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;

    //Get
    [BasicAuthorization, HttpGet, Route("GetCustomers/{pageNumber:int}/{pageSize:int}")]
    public IHttpActionResult GetCustomers(int pageNumber, int pageSize)
    {
        objCust = new CustomersMgt();
        return Json(objCust.GetCustomer(pageNumber, pageSize));
    }

    //Post
    [BasicAuthorization, HttpPost, Route("SaveCustomer")]
    public IHttpActionResult SaveCustomer(Customer model)
    {
        objCust = new CustomersMgt();
        return Json(objCust.SaveCustomer(model));
    }

    //Put
    [BasicAuthorization, HttpPut, Route("UpdateCustomer")]
    public IHttpActionResult UpdateCustomer(Customer model)
    {
        objCust = new CustomersMgt();
        return Json(objCust.UpdateCustomer(model));
    }

    //Delete
    [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) {

    //Token Generate ClientEnd
    $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()
            };

            //compare token
            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! Smile | :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here