Click here to Skip to main content
15,881,898 members
Articles / Web Development / ASP.NET

Building ASP.NET Web API RESTful Service – Part 8

Rate me:
Please Sign up or sign in to vote.
4.71/5 (5 votes)
5 Mar 2014CPOL7 min read 51.4K   36   4
This is the eight part of Building ASP.NET Web API RESTful Service Series.

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 a 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 below:

C#
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 a small HTML message in the response body informing the 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 a small HTML message informing the 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:

C#
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 shown below:

C#
//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 candidate 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 issues 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 himself. 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 usernameKhaledHassan” 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 means 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 is 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 from being exposed to others, Basic Authentication should always be used over SSL connection (HTTPS).

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

C#
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 is 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 subsequent requests, the user is 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) handles 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:

C#
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 correct credentials, we’ll receive status code 200 along with complete JSON object graph which contains all specific data for this user. For any subsequent requests 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.

The 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)


Written By
Architect App Dev Consultant
Jordan Jordan
Working for Microsoft Consultation Services as a Lead App Dev Consultant.

I have more than 15 years of experience in developing and managing different software solutions for the finance, transportation, logistics, and e-commerce sectors. I’ve been deeply involved in .NET development since early framework versions and currently, I work on different technologies on the ASP.NET stack with a deep passion for Web API, Distributed systems, Microservices, and Microsoft Azure.

Prior to joining Microsoft, I have been awarded the Microsoft Most Valuable Professional (MVP) Award for the years 2015 and 2016 in Visual Studio and Development Technologies, also I’m a regular speaker in local events and Dev user groups.

Comments and Discussions

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

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.