Click here to Skip to main content
13,767,443 members
Click here to Skip to main content
Add your own
alternative version

Stats

20.9K views
222 downloads
13 bookmarked
Posted 3 Jul 2016
Licenced CPOL

Authorization Filters in ASP.NET Web API 2 & AngularJS

, 4 Jul 2016
Rate this:
Please Sign up or sign in to vote.
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! :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Shashangka Shekhar
Team Leader Royal Green Ltd
Bangladesh Bangladesh
Hi, I am Shashangka Shekhar,

Working with Microsoft Technologies. Since March 2011, it was my first step to working with Microsoft Technologies, achieved bachelor’s degree on Computer Science from State University of Bangladesh(Dhaka). Have 7+ years of professional experience, currently working as Team Lead at Royal Green Ltd.

I believe in desire of learning & also love to be a part of .Net Community by sharing knowledge’s.

You may also be interested in...

Comments and Discussions

 
Question401 status code Pin
sheetal20007-Aug-18 16:26
membersheetal20007-Aug-18 16:26 
Questionauthorization header value Pin
Harsh Agarwal128-Apr-17 3:11
memberHarsh Agarwal128-Apr-17 3:11 
QuestionWell, db-script attachment can't be download? Pin
cao_zq4-Jul-16 5:57
membercao_zq4-Jul-16 5:57 
AnswerRe: Well, db-script attachment can't be download? Pin
Shashangka Shekhar4-Jul-16 6:20
professionalShashangka Shekhar4-Jul-16 6:20 
GeneralRe: Well, db-script attachment can't be download? Pin
cao_zq4-Jul-16 6:40
membercao_zq4-Jul-16 6:40 
GeneralRe: Well, db-script attachment can't be download? Pin
Shashangka Shekhar4-Jul-16 7:33
professionalShashangka Shekhar4-Jul-16 7:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.181116.1 | Last Updated 4 Jul 2016
Article Copyright 2016 by Shashangka Shekhar
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid