Thursday, April 12, 2018

Web API Security

There are two technique for security in Web API. one is basic authentication and second is token based 
authorization. We can also maintain session using token based atuhorization.

Authentication is a technique where user id and password has been passed. If you are sending user id, password
in plain test inside request header, it is prone to hack (CSRF Cross site request forgery).


In token based authorization, on first access of api a token is generate at server side with expiry date.
After that this token send with each request no need to send credential each time. Token can be generated using GUID.
This can be saved in database or some external file. 

Basic authentication can be implemented with a simple class. Here BasicAuthenticationIdentity  is a user defined class which has user id and
password.

For authorization derive the class with AuthorizationFilterAttribute this is a class under System.Web.Http.Filters.
You need to override OnAuthorization function.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class GenericAuthenticationFilter : AuthorizationFilterAttribute
{

    ///
    /// Public default Constructor
    ///
    public GenericAuthenticationFilter()
    {
    }

    private readonly bool _isActive = true;

    ///
    /// parameter isActive explicitly enables/disables this filetr.
    ///
    ///
    public GenericAuthenticationFilter(bool isActive)
    {
        _isActive = isActive;
    }

    ///
    /// Checks basic authentication request
    ///
    ///
    public override void OnAuthorization(HttpActionContext filterContext)
    {
        if (!_isActive) return;
        var identity = FetchAuthHeader(filterContext);
        if (identity == null)
        {
            ChallengeAuthRequest(filterContext);
            return;
        }
        var genericPrincipal = new GenericPrincipal(identity, null);
        Thread.CurrentPrincipal = genericPrincipal;
        if (!OnAuthorizeUser(identity.Name, identity.Password, filterContext))
        {
            ChallengeAuthRequest(filterContext);
            return;
        }
        base.OnAuthorization(filterContext);
    }

    ///
    /// Virtual method.Can be overriden with the custom Authorization.
    ///
    ///
    ///
    ///
    ///
    protected virtual bool OnAuthorizeUser(string user, string pass, HttpActionContext filterContext)
    {
        if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pass))
            return false;
        return true;
    }

    ///
    /// Checks for autrhorization header in the request and parses it, creates user credentials and returns as BasicAuthenticationIdentity
    ///
    ///
    protected virtual BasicAuthenticationIdentity FetchAuthHeader(HttpActionContext filterContext)
    {
        string authHeaderValue = null;
        var authRequest = filterContext.Request.Headers.Authorization;
        if (authRequest != null && !String.IsNullOrEmpty(authRequest.Scheme) && authRequest.Scheme == "Basic")
            authHeaderValue = authRequest.Parameter;
        if (string.IsNullOrEmpty(authHeaderValue))
            return null;
        authHeaderValue = Encoding.Default.GetString(Convert.FromBase64String(authHeaderValue));
        var credentials = authHeaderValue.Split(':');
        return credentials.Length < 2 ? null : new BasicAuthenticationIdentity(credentials[0], credentials[1]);
    }


    ///
    /// Send the Authentication Challenge request
    ///
    ///
    private static void ChallengeAuthRequest(HttpActionContext filterContext)
    {
        var dnsHost = filterContext.Request.RequestUri.DnsSafeHost;
        filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        filterContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", dnsHost));
    }
}

Now create ApiAuthorizationFilter class

using System.Threading;
using System.Web.Http.Controllers;
using BusinessServices;

namespace WebApi.Filters
{
    ///
    /// Custom Authentication Filter Extending basic Authentication
    ///
    public class ApiAuthenticationFilter : GenericAuthenticationFilter
    {
        ///
        /// Default Authentication Constructor
        ///
        public ApiAuthenticationFilter()
        {
        }

        ///
        /// AuthenticationFilter constructor with isActive parameter
        ///
        ///
        public ApiAuthenticationFilter(bool isActive)
            : base(isActive)
        {
        }

        ///
        /// Protected overriden method for authorizing user
        ///
        ///
        ///
        ///
        ///
        protected override bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
        {
            var provider = actionContext.ControllerContext.Configuration
                               .DependencyResolver.GetService(typeof(IUserServices)) as IUserServices;
            if (provider != null)
            {
                var userId = provider.Authenticate(username, password);
                if (userId>0)
                {
                    var basicAuthenticationIdentity = Thread.CurrentPrincipal.Identity as BasicAuthenticationIdentity;
                    if (basicAuthenticationIdentity != null)
                        basicAuthenticationIdentity.UserId = userId;
                    return true;
                }
            }
            return false;
        }
    }
}

There are three ways in which you can use this authentication filter.

Just apply this filer to ProductController. You can add this filter at the top of the controller, for all API requests to be validated,

Hide   Copy Code
[ApiAuthenticationFilter]
[RoutePrefix("v1/Products/Product")]
public class ProductController : ApiController
You can also globally add this in Web API configuration file , so that filter applies to all the controllers and all the actions associated to it,

Hide   Copy Code
GlobalConfiguration.Configuration.Filters.Add(new ApiAuthenticationFilter());
You can also apply it to Action level too by your wish to apply or not apply authentication to that action,

Hide   Copy Code
// GET api/product
 [ApiAuthenticationFilter(true)]
 [GET("allproducts")]
 [GET("all")]
 public HttpResponseMessage Get()
 {
      …………………
 }
 // GET api/product/5
  [ApiAuthenticationFilter(false)]
 [GET("productid/{id?}")]
 [GET("particularproduct/{id?}")]
 [GET("myproduct/{id:range(1, 3)}")]
 public HttpResponseMessage Get(int id)
 {
      ……………………..
 } 

No comments:

Followers

Link