Mvc web api token based authentication

Now days web api of mvc is using more than the wcf services. So when the security of api come to mind then everyone try to make api calls secure so no one can hack and access the api without access. So I implemented the token based authentication to allow only authenticated user to access the api not the other users.

I am returning the token on login method and also created a authorize attribute to check token is valid or not for the next calls.

Download Sample code

Login method :-

using System;
using System.Web;
using System.Web.Http;
using TokenBasedAuthentication.Helpers;

namespace TokenBasedAuthentication.Controllers
{
    public class AccountController : ApiController
    {
        //
        // GET: /Account/

        [System.Web.Http.HttpGet]
        public IHttpActionResult Login(string email, string password = "password")
        {
            try
            {
                if (!string.IsNullOrEmpty(email))
                {
                    var ip = HttpContext.Current.Request.UserHostAddress;
                    var agent = HttpContext.Current.Request.UserAgent;
                    var ticks = DateTime.UtcNow.Ticks; ;
                    var token = SecurityManager.GenerateToken(email, password, ip, agent, ticks);
                    return Ok(new { token });
                }
                return Unauthorized();
            }
            catch (Exception ex)
            {
                return Json(new { Success = false, Response = ex.Message });
            }
        }
    }
}

Next we need to create the authorize attribute in Filter folder, see the below code :-

using System;
using System.Web;
using System.Web.Http.Controllers;
using TokenBasedAuthentication.Helpers;

namespace TokenBasedAuthentication.Filters
{
    
    //[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
    public class RESTAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
    {
        private const string SecurityToken = "token";

        public override void OnAuthorization(HttpActionContext filterContext)
        {
            if (Authorize(filterContext))
            {
                return;
            }

            HandleUnauthorizedRequest(filterContext);
        }

        protected void HandleUnauthorizedRequest(HttpActionContext filterContext)
        {
            base.HandleUnauthorizedRequest(filterContext);
        }

        private static bool Authorize(HttpActionContext actionContext)
        {
            if (actionContext == null) throw new ArgumentNullException("actionContext");
            try
            {
                string token = null;
                var queryString = actionContext.Request.RequestUri.Query;
                if (!string.IsNullOrWhiteSpace(queryString))
                {
                    token = HttpUtility.ParseQueryString(
                                         queryString.Substring(1))[SecurityToken];
                }

                var ip = HttpContext.Current.Request.UserHostAddress;
                var agent = HttpContext.Current.Request.UserAgent;
                return SecurityManager.IsTokenValid(token, ip, agent);
            }
            catch (Exception)
            {
                return false;
            }
        }
    }
}

We also need to create the Helpers as well for validate the token. I created SecurityManager.cs under the Helpers folder.

using System;
using System.Security.Cryptography;
using System.Text;


namespace TokenBasedAuthentication.Helpers
{
    public static class SecurityManager
    {
        private const string Alg = "HmacSHA256";
        private const string Salt = "rz8LuOtFBXphj9WQfvFh";
        private const int ExpirationMinutes = 10;

        
        public static string GenerateToken(string username, string password, string ip, string userAgent, long ticks)
        {
            var hash = string.Join(":", new string[] { username, ip, userAgent, ticks.ToString() });
            string hashLeft;
            string hashRight;

            using (HMAC hmac = HMACSHA256.Create(Alg))
            {
                hmac.Key = Encoding.UTF8.GetBytes(GetHashedPassword(password));
                hmac.ComputeHash(Encoding.UTF8.GetBytes(hash));

                hashLeft = Convert.ToBase64String(hmac.Hash);
                hashRight = string.Join(":", new string[] { username, ticks.ToString() });
            }

            return Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(":", hashLeft, hashRight)));
        }

       
        public static string GetHashedPassword(string password)
        {
            var key = string.Join(":", new string[] { password, Salt });

            using (HMAC hmac = HMACSHA256.Create(Alg))
            {
                // Hash the key.
                hmac.Key = Encoding.UTF8.GetBytes(Salt);
                hmac.ComputeHash(Encoding.UTF8.GetBytes(key));

                return Convert.ToBase64String(hmac.Hash);
            }
        }

      
        public static bool IsTokenValid(string token, string ip, string userAgent)
        {
            var result = false;

            try
            {
                // Base64 decode the string, obtaining the token:username:timeStamp.
                var key = Encoding.UTF8.GetString(Convert.FromBase64String(token));

                // Split the parts.
                var parts = key.Split(new char[] { ':' });
                if (parts.Length == 3)
                {
                    // Get the hash message, username, and timestamp.
                    //  var hash = parts[0];
                    var username = parts[1];
                    var ticks = long.Parse(parts[2]);
                    var timeStamp = new DateTime(ticks);

                    // Ensure the timestamp is valid.
                    var expired = Math.Abs((DateTime.UtcNow - timeStamp).TotalMinutes) > ExpirationMinutes;
                    if (!expired)
                    {
                        //
                        // Lookup the user's account from the db.
                        //
                        var password = "password";//password from your database
                        // Hash the message with the key to generate a token.
                        var computedToken = GenerateToken(username, password, ip, userAgent, ticks);

                        // Compare the computed token with the one supplied and ensure they match.
                        result = (token == computedToken);

                    }
                }
            }
            catch
            {
                //return false;
            }

            return result;
        }
    }
}

Now to validate the controllers calls, the token passed is valid or not. We need to add RESTAuthrize attribute. see below controller example :-

using System.Collections.Generic;
using System.Web.Http;
using TokenBasedAuthentication.Filters;

namespace TokenBasedAuthentication.Controllers
{
    [RESTAuthorize]
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        public void Delete(int id)
        {
        }
    }
}

I also created the sample code if you need the sample code working. Please have a look over the github Download Sample code

Posted by | View Post | View Group