Click here to Skip to main content
11,580,081 members (73,757 online)
Click here to Skip to main content

Building ASP.NET Web API RESTful Service – Part 8

, 5 Mar 2014 CPOL 28.4K 32
Rate this:
Please Sign up or sign in to vote.
This is the eight part of Building ASP.Net Web API RESTful Service Series. The topics we’ll cover are: Building the Database Model using Entity Framework Code First – Part 1. Applying the Repository Pattern for the Data Access Layer – Part 2. Getting started with ASP.Net Web API -

This is the eight part of Building ASP.NET Web API RESTful Service Series. The topics we’ll cover are:

Update (2014-March-5) Two new posts which cover ASP.Net Web API 2 new features:

Securing Web API

In this post we’ll talk about securing our eLearning API, till this moment all requests sent from client to the API are done over HTTP protocol (http://) and the communication is not encrypted, but in this post we’ll implement authentication feature in “StudentsController” so we’ll be sending Username and Password for authenticating students. It is well known that transmitting confidential information should be done using secure HTTP (https://).

Enforce HTTPS for Web API

We can enforce HTTPS on the entire Web API by configuring this on IIS level, but in some scenarios you might enforce HTTPS on certain methods where we transmit confidential information and use HTTP for other methods.

In order to implement this we need to use Web API filters; basically filters will allow us to execute some code in the pipeline before the execution of code in controller methods. This new filter will be responsible to examine if the URI scheme is secured, and if it is not secure, the filter will reject the call and send response back to the client informing him that request should be done over HTTPS.

We’ll add a new filter which derives from AuthorizationFilterAttribute, this filter contains an overridden method called OnAuthorization where we can inject a new response in case the request is not done over HTTPS.

Let’s add new folder named Filters to project root then add new class named ForceHttpsAttribute which derives from System.Web.Http.Filters.AuthorizationFilterAttribute.

The code for the filter class will be as the below:

public class ForceHttpsAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var request = actionContext.Request;

        if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
        {
            var html = "<p>Https is required</p>";

            if (request.Method.Method == "GET")
            {
                actionContext.Response = request.CreateResponse(HttpStatusCode.Found);
                actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");

                UriBuilder httpsNewUri = new UriBuilder(request.RequestUri);
                httpsNewUri.Scheme = Uri.UriSchemeHttps;
                httpsNewUri.Port = 443;

                actionContext.Response.Headers.Location = httpsNewUri.Uri;
            }
            else
            {
                actionContext.Response = request.CreateResponse(HttpStatusCode.NotFound);
                actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
            }

        }
    }
}

By looking at the code above we are using “actionContext” parameter to get the request and response object from it. What we are doing here is examining the URI scheme of the request, so if it is not secure (http://) we need to return small html message in the response body informing client to send the request again over https.

As well we are differentiating between the GET method, and other methods (POST, PUT, DELETE); because in case the client initiated a GET request to existing resource over http we need to construct the same request again using https scheme and use 443 SSL port then inject this secure URI in response location header. By doing this the client (browser) will initiate automatically another GET request using https scheme .

In case of non GET requests, we will return 404 status code (Not Found) and small html message informing client to send the request again over HTTPS.

Now if we want to enforce this filter over the entire Web API we need to add this filter globally in WebAPIConfig class as the code below:

public static void Register(HttpConfiguration config)
{
   config.Filters.Add(new ForceHttpsAttribute());
}

But if we want to enforce HTTPS for certain methods or certain controllers we can add this filter attribute ForceHttps as the below:

//Enforce HTTPS on the entire controller
[Learning.Web.Filters.ForceHttps()]
public class CoursesController : BaseApiController
{
    //Enforce HTTPS on POST method only
    [Learning.Web.Filters.ForceHttps()]
    public HttpResponseMessage Post([FromBody] CourseModel courseModel)
    {
    }
}

Authenticate Users using Basic Authentication

Until this moment all methods in our API are public, and any user on the internet is able to request any resource, but in real life scenario this is not correct, specific data should be accessed by specific people, so we need to authenticate requests on certain resources. A good candidates in our sample API for clients authentications are:

  1. Clients who make a GET request to the URI “http://{your_port}/api/students/{userName}“. This means that if a client issue a request to GET detailed information about student with username “TaiseerJoudeh”, then he needs to provide the username and password for this resource in the request to authenticate him self. As well we won’t allow authenticated user “TaiseerJoudeh” to GET detailed information for another resource because he is not the resource owner and he shouldn’t be able to see his detailed information such as Email, Birth date, classes enrolled in, etc…
  2. Clients who make a POST request the URI “http://{your_port}/api/courses/2/students/{userName}“. This method is used to enroll specific student in specific class, it makes sense to authenticate requests here because if student with username “KhaledHassan” wants to enroll in course with Id 2, then he needs to authenticate himself by providing username and password. Otherwise anyone will be able to enroll any student in any class.

In our scenario we’ll use the Basic Authentication to authenticate users requesting the above two resources, to do so we need to add new Web API filter which will be responsible to read authorization data from request header, check if the authentication type is “basic”, validate credentials sent in the authorization headers, and finally authenticate user if all is good. Otherwise it will return a response with status code 401 (Unauthorized) and it won’t return the resource.

Before digging into the code, let’s talk a little bit about basic authentication.

What is Basic Authentication?

It provides a mean to authenticate the sender of the request before actually processing the HTTP request. This will protect the server against Denial of service attacks (DoS). The way it works that the client who is initiating the HTTP request provides a username and password which is base64-encoded and placed in the HTTP header as string on the form “username:password”, the recipient of the message (server) is expected to first authenticate the credentials and process the request further only when the authentication is successful.

As the username and password are only base64-encoded and to avoid passwords are exposed to others, Basic Authentication should be always used over SSL connection (HTTPS).

To apply this in our API let’s add new class named LearningAuthorizeAttribute which derives from System.Web.Http.Filters.AuthorizationFilterAttribute.

public class LearningAuthorizeAttribute : AuthorizationFilterAttribute
{
    [Inject]
    public LearningRepository TheRepository { get; set; }

    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        //Case that user is authenticated using forms authentication
        //so no need to check header for basic authentication.
        if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
        {
            return;
        }

        var authHeader = actionContext.Request.Headers.Authorization;

        if (authHeader != null)
        {
            if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) &&
                !String.IsNullOrWhiteSpace(authHeader.Parameter))
            {
                var credArray = GetCredentials(authHeader);
                var userName = credArray[0];
                var password = credArray[1];

                if (IsResourceOwner(userName, actionContext))
                {
                    //You can use Websecurity or asp.net memebrship provider to login, for
                    //for he sake of keeping example simple, we used out own login functionality
                    if (TheRepository.LoginStudent(userName, password))
                    {
                        var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null);
                        Thread.CurrentPrincipal = currentPrincipal;
                        return;
                    }
                }
            }
        }

        HandleUnauthorizedRequest(actionContext);
    }

    private string[] GetCredentials(System.Net.Http.Headers.AuthenticationHeaderValue authHeader)
    {

        //Base 64 encoded string
        var rawCred = authHeader.Parameter;
        var encoding = Encoding.GetEncoding("iso-8859-1");
        var cred = encoding.GetString(Convert.FromBase64String(rawCred));

        var credArray = cred.Split(':');

        return credArray;
    }

    private bool IsResourceOwner(string userName, System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var routeData = actionContext.Request.GetRouteData();
        var resourceUserName = routeData.Values["userName"] as string;

        if (resourceUserName == userName)
        {
            return true;
        }
        return false;
    }

    private void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);

        actionContext.Response.Headers.Add("WWW-Authenticate",
            "Basic Scheme='eLearning' location='http://localhost:8323/account/login'");

    }
}

In the code above we’ve overridden the method OnAuthorization and implemented the below:

  1. Getting the authorization data from request headers
  2. Making sure that authorization header scheme is set to “basic” authentication and contains base64-encoded string.
  3. Converting the base64-encoded string to a string on the form “username:password” and get the username and password.
  4. Validating that username sent in authentication header is the same for username in URI to ensure that resource owner only able to view his details.
  5. Validating the credentials against our database.
  6. If the credentials are correct we set the identity for current principal, so in sub subsequent requests the user already authenticated.
  7. If the credentials are incorrect the server sends an HTTP response with a 401 status code (Unauthorized), and a WWW-Authenticate header. Web clients (browser) handle this response by requesting a user ID and password from client.

Now to apply basic authentication on the two methods we mentioned earlier, all we need to do is to add this attribute “LearningAuthorizeAttribute” to those methods as the code below:

public class StudentsController : BaseApiController
{
    [LearningAuthorizeAttribute]
    public HttpResponseMessage Get(string userName)
    {

    }
}

public class EnrollmentsController : BaseApiController
{
    [LearningAuthorizeAttribute]
    public HttpResponseMessage Post(int courseId, 
      [FromUri]string userName, [FromBody]Enrollment enrollment)
    {

    }
}

Now we need to test authentication by sending GET request to URI: “http://localhost:{your_port}/api/students/TaiseerJoudeh” using two different clients, Firefox and Fiddler.

Testing user Firefox:

We’ll issue get request using the browser and the response returned will be 401 because we didn’t provide username and password and “Authentication Required” prompt will be displayed asking for the username and password. By providing the the correct credentials we’ll receive status code 200 along with complete JSON object graph which contains all specific data for for this user. For any sub subsequent request for the same resource the code will not check credentials because we have created principal for this user and he is already authenticated.

Testing using Fiddler:

By using fiddler we need to create the base64-encoded string which contains the “username:password” and send it along with the authorization header, to generate this string we will use http://www.base64encode.org/ as the image below:

Web API Basic Authentication

Note that this is not encryption at all, so as we stated earlier, using Basic Authentication should be done over SSL only.

Now we will use the encoded string to pass it in authorization header “Authorization: Basic VGFpc2VlckpvdWRlaDpZRUFSVkZGTw==” the request will be as the image below:

Web API Basic Authorization Header

The response code will be 200 OK and will receive the specific data for this authenticated user.

In the next post we’ll talk about versioning Web API and how we can implement different techniques to implement versioning.

Source code is available on GitHub.

License

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

Share

About the Author

Taiseer Joudeh
Architect Aramex
Jordan Jordan
Taiseer Joudeh has more than 8 years of experience spent in developing and managing different software solutions for finance, transportation, logistics, and e-commerce sectors. He has been deeply involved in .NET development since early framework versions and currently he works on different technologies on the ASP.NET stack with deep passion for Web API, and Microsoft Azure.

Recently Taiseer has been focusing on building Single Page Applications and Hybrid Mobile Solutions using AngularJS.

Taiseer lives in Jordan with his wife and son, works as IT Manager at Aramex, also he is a regular speaker in local events and Dev user groups, he is a avid blogger on http://bitoftech.net, and you can follow him on twitter @tjoudeh

You may also be interested in...

Comments and Discussions

 
QuestionAction Filter Pin
Member 1102927822-Aug-14 0:30
memberMember 1102927822-Aug-14 0:30 
SuggestionSeparation of conserns Pin
DoMuX2-Apr-14 3:52
memberDoMuX2-Apr-14 3:52 
QuestionEmbedded Token Endpoint Pin
aadams998-Dec-13 17:57
memberaadams998-Dec-13 17:57 
AnswerRe: Embedded Token Endpoint Pin
Taiseer Joudeh14-Dec-13 5:17
memberTaiseer Joudeh14-Dec-13 5:17 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150603.1 | Last Updated 5 Mar 2014
Article Copyright 2013 by Taiseer Joudeh
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid