Advertisement
Scroll to top

Once you have your Web API developed, before exposing it to your clients, based upon your needs you may need to secure some or all parts of your API Service so that only verified users can access your API service. This securing in ASP.NET can be achieved using the authentication and authorization mechanisms.

Authentication

Authentication is the process of determining whether someone or something is, in fact, who or what it is claimed to be. By using the authentication mechanism, we make sure that every request received by the Web API service is sent from a client with proper credentials.

Authentication Using Message Handlers

A message handler is a class that receives an HTTP request and returns an HTTP response. Message handlers are derived classes from the abstract class HttpMessageHandler. They are good for cross-cutting concerns that operate at the level of HTTP messages (rather than controller actions). For example, a message handler might:

  • read or modify request headers
  • add a response header to responses
  • validate requests before they reach the controller

In a Web API, typically, a series of message handlers are chained together, forming a pattern called delegating handler.

HTTP Request flow through Message HandlersHTTP Request flow through Message HandlersHTTP Request flow through Message Handlers

The order in which these handlers are set up is important as they will be executed sequentially. 

The most important handler sits at the very top, guarding everything that comes in. If the checks pass, it will pass this request down the chain to the next delegating handler, and so on. 

If all goes well, it will then arrive at the API Controller and execute the desired action. However, if any of the checks fail within the handlers, then the request is denied and a response is sent to the client.

With this much theory in hand, now let's write code for our handlers. We will create two message handlers in this article:

  1. APIKeyHandler: Handler responsible for intercepting an HTTP request and ensuring its header contains an API key
  2. AuthHandler: Handler responsible for authenticating a user's credentials and roles

API Key Authentication

In your Web API project, create a folder called MessageHandlers and add a class APIKeyHandler.cs.

1
public class APIKeyHandler : DelegatingHandler
2
    {
3
        //set a default API key 

4
        private const string yourApiKey = "X-some-key";
5
6
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
7
        {
8
            bool isValidAPIKey = false;
9
            IEnumerable<string> lsHeaders;
10
            //Validate that the api key exists

11
12
            var checkApiKeyExists = request.Headers.TryGetValues("API_KEY", out lsHeaders);
13
14
            if (checkApiKeyExists)
15
            {
16
                if (lsHeaders.FirstOrDefault().Equals(yourApiKey))
17
                {
18
                    isValidAPIKey = true;
19
                }                
20
            }
21
                
22
            //If the key is not valid, return an http status code.

23
            if (!isValidAPIKey)
24
                return request.CreateResponse(HttpStatusCode.Forbidden, "Bad API Key");
25
26
            //Allow the request to process further down the pipeline

27
            var response = await base.SendAsync(request, cancellationToken);
28
29
            //Return the response back up the chain

30
            return response;
31
        }
32
    }

The APIKeyHandler.cs inherits from DelegatingHandler, which in turn inherits from HttpMessageHandler. This allows us to override the functionality for inspecting an HTTP request and control whether we want to allow this request to flow down the pipeline to the next handler and controller or halt the request by sending a custom response.

In this class, we are achieving this by overriding the SendAsync method. This method looks for an API key (API_KEY) in the header of every HTTP request, and passes the request to the controller only if a valid API key is present in the request header.

Now, to see this handler in action, we need to first register it to our application in the Application_Start method from the Global.asax file.

1
GlobalConfiguration.Configuration.MessageHandlers.Add(new APIKeyHandler());

Try calling any method that you have exposed through your Web API controllers and you should see "Bad API Key" as response.

For a demo in this article, I am using the same project and the URLs that I have created in my previous article, "Developing an ASP.NET Web API".

API Key Handler demoAPI Key Handler demoAPI Key Handler demo

Let's verify that the APIKeyHandler is working alright by creating an HTTP Request with correct headers. For that, we need to create an HTTP header with key value:

"API_KEY" : "X-some-key"

I am using a Mozilla browser plugin called "HTTP Tool" for creating HTTP request headers here.

HTTP Header with HTTP ToolHTTP Header with HTTP ToolHTTP Header with HTTP Tool

The HTTP request is now passed all the way to the controller by the handler.

So our API key check handler is in place now. This secures our Web API to make sure only those clients that are provided with valid API keys can access this service. Next we will look at how we can implement security based on user roles.

Basic Authentication

Basic authentication, as its name suggests, is the most simple and basic form of authenticating HTTP requests. The client sends Base64-encoded credentials in the Authorize header on every HTTP request, and only if the credentials are verified does the API return the expected response. Basic authentication doesn't require server-side session storage or implementation of cookies as every request is verified by the API.

Once basic authentication implementation in Web API is understood, it will be very easy to hook other forms of authentication. Only the authentication process will be different, and the Web API hooks, where it is done, will be the same.

For verifying user credentials, we create an IPrincipal object which represents the current security context.

Add a new folder called Security and a new class TestAPIPrincipal.cs in it.

1
public class TestAPIPrincipal : IPrincipal
2
    {
3
        //Constructor

4
        public TestAPIPrincipal(string userName)
5
        {
6
            UserName = userName;
7
            Identity = new GenericIdentity(userName);
8
        }
9
10
        public string UserName { get; set; }
11
        public IIdentity Identity { get; set; }
12
        public bool IsInRole(string role)
13
        {
14
            if (role.Equals("user"))
15
            {
16
                return true;
17
            }
18
            else
19
            {
20
                return false;
21
            }
22
        }        
23
    }

The IIdentity object associated with the principal has a property called IsAuthenticated. If the user is authenticated, this property will return true; otherwise, it will return false.

Now, let's create another handler called AuthHandler.cs.

1
public class AuthHandler : DelegatingHandler
2
    {
3
        string _userName = "";
4
        
5
        //Method to validate credentials from Authorization

6
        //header value

7
        private bool ValidateCredentials(AuthenticationHeaderValue authenticationHeaderVal)
8
        {
9
            try
10
            {
11
                if (authenticationHeaderVal != null
12
                    && !String.IsNullOrEmpty(authenticationHeaderVal.Parameter))
13
                {
14
                    string[] decodedCredentials
15
                    = Encoding.ASCII.GetString(Convert.FromBase64String(
16
                    authenticationHeaderVal.Parameter))
17
                    .Split(new[] { ':' });
18
19
                    //now decodedCredentials[0] will contain

20
                    //username and decodedCredentials[1] will

21
                    //contain password.

22
23
                    if (decodedCredentials[0].Equals("username")
24
                    && decodedCredentials[1].Equals("password"))
25
                    {
26
                        _userName = "John Doe";
27
                        return true;//request authenticated.

28
                    }
29
                }
30
                return false;//request not authenticated.

31
            }
32
            catch {
33
                return false;
34
            }
35
        }
36
37
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
38
        {
39
            //if the credentials are validated,

40
            //set CurrentPrincipal and Current.User

41
            if (ValidateCredentials(request.Headers.Authorization))
42
            {
43
                Thread.CurrentPrincipal = new TestAPIPrincipal(_userName);
44
                HttpContext.Current.User = new TestAPIPrincipal(_userName);
45
            }
46
            //Execute base.SendAsync to execute default

47
            //actions and once it is completed,

48
            //capture the response object and add

49
            //WWW-Authenticate header if the request

50
            //was marked as unauthorized.

51
52
            //Allow the request to process further down the pipeline

53
            var response = await base.SendAsync(request, cancellationToken);
54
55
            if (response.StatusCode == HttpStatusCode.Unauthorized
56
                && !response.Headers.Contains("WwwAuthenticate"))
57
            {
58
                response.Headers.Add("WwwAuthenticate", "Basic");
59
            }
60
61
            return response;
62
        }
63
    }

This class contains a private method ValidateCredentials, which checks for decoded username and password values from the HTTP request header, and also the SendAsync method for intercepting the HTTP request.

If the credentials of the client are valid, then the current IPrincipal object is attached to the current thread, i.e. Thread.CurrentPrincipal. We also set the HttpContext.Current.User to make the security context consistent. This allows us to access the current user's details from anywhere in the application.

Once the request is authenticated, base.SendAsync is called to send the request to the inner handler. If the response contains an HTTP unauthorized header, the code injects a WwwAuthenticate header with the value Basic to inform the client that our service expects basic authentication.

Now, we need to register this handler in the Global.Asax class as we did for our ApiKeyHandler. Make sure that the AuthHandler handler is below the first handler registration to make sure of the right order.

1
GlobalConfiguration.Configuration.MessageHandlers.Add(new APIKeyHandler());
2
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthHandler());

But before we can see basic authentication in action, we will first need to implement authorization.

Authorization

Authorization is verifying whether the authenticated user can perform a particular action or consume a particular resource. This process in Web API happens later in the pipeline, after
authentication and before the controller actions are executed.

Using Authorize Attribute

ASP.NET MVC Web API provides an authorization filter called AuthorizeAttribute which verifies the request's IPrincipal, checks its Identity.IsAuthenticated property, and returns a 401 Unauthorized HTTP status if the value is false and the requested action method will not be executed. This filter can be applied in different levels like the controller level or action level, and can be easily applied using the [Authorize] syntax on top of controllers or actions.

1
[Authorize]
2
public class ClassifiedsController : ApiController

Once this attribute is set, it will prevent all action methods in the controller from being accessed by unauthorized users.

First our basic authentication handler kicks in to set the current user's identity IPrincipal object. Then, before this request reaches the controller, AuthorizeAttribute verifies access to the particular controller/action for the current user.

To see this in action, first let's create an HTTP request without proper credentials.

Access denied for unauthorized usersAccess denied for unauthorized usersAccess denied for unauthorized users

The access gets denied by the AuthorizeAttribute.

Now, let's create another request with Authorization header key/value this time as follows:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Here, the value dXNlcm5hbWU6cGFzc3dvcmQ= is the Base64-encoded form of username:password.

This request gets access rights to the controller/action as expected.

Access granted for user with proper credentialsAccess granted for user with proper credentialsAccess granted for user with proper credentials

This is an example of securing the entire controller's public Actions.

Action Level Authorization

We can also restrict some parts of the controller actions by setting the [Authorize] attribute at the action level only instead. Doing so will allow us to have both protected and unprotected actions in the same controller.

1
//[Authorize]

2
    public class ClassifiedsController : ApiController
3
    {
4
        public List<ClassifiedModel> Get(string id)
5
        {
6
            return ClassifiedService.GetClassifieds(id);
7
        }
8
9
        [Authorize]
10
        public List<ClassifiedModel> Get()
11
        {
12
            return ClassifiedService.GetClassifieds("");
13
        }

[AllowAnonymous] Attribute

Another way to have both protected and unprotected actions within the controller is by making use of the [AllowAnonymous] attribute. When we set the [Authorize] attribute in the controller level and set the [AllowAnonymous] attribute for any action inside the controller, that action will skip the [Authorize] attribute.

Role and User Checks

It is also possible to filter certain roles and users for access rights. For example, we can have something like [Authorize(Roles = "Admin")] on the controllers and actions.

Custom Authorization Attribute

Finally, we can also create our own custom authorization attribute depending upon our needs. One of the ways to achieve this is by extending AuthorizeAttribute.

Say we want to restrict our Web API service to only certain parts of the world by restricting access to users that are not within a certain range of IP address. We can create a custom authorize attribute for this purpose by deriving from the AuthorizeAttribute class and overriding the IsAuthorized method.

1
public class RestrictIPsAttribute: System.Web.Http.AuthorizeAttribute
2
    {
3
        protected override bool IsAuthorized(HttpActionContext context)
4
        {
5
            var ip = HttpContext.Current.Request.UserHostAddress;
6
7
            //check for ip here

8
            if (ip.Contains(""))
9
            {
10
                return true;
11
            }
12
            return false;
13
        }
14
    }

Once we have our custom Authorize attribute, we can decorate our controllers/actions with it.

1
[RestrictIPsAttribute]
2
public List<ClassifiedModel> Get()
3
{
4
    return ClassifiedService.GetClassifieds("");
5
}

Conclusion

In this article, we looked at how we can secure our ASP.NET Web API service before exposing the service to the world outside. We looked at how we can authenticate HTTP requests for valid API keys and for valid user credentials. With this much knowledge in hand, I believe we ready to develop any custom security for our APIs.

For those of you who are either just getting started with Laravel or looking to expand your knowledge, site, or application with extensions, we have a variety of things you can study in Envato Market.

I hope you enjoyed reading as much as learning from this article, and remember to leave any questions or comments in the feed below!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.