This article talk s about securing ASP.net Restful web services, this is the first article of the series “Securing asp.net web applications”. This article talks about implementing Bearer Token-based Authentication and Authorization.

Authentication

Is the process of identifying the user and validating their credentials. If we map the Authentication to the example in this article, the process of validating user credentials and generating an access token is called Authorization.

Authorization

Is the process of determining if the user can access the requested resource. If we map the Authentication to the example in this article, the process of reading given Authorization token and determining if the user has access to requested Web API controller/ action is called Authorization.

Whats wrong with cookies? Why do we need token based authorization?

There is nothing wrong with cookies. Cookies are only usable in browser-based web applications. The RESTful web services (Web API) can be used on every platform that has an internet connection. Cookies might not be the right fit for those platforms (e.g. mobile applications). Embedding information in an interchangeable data format is far better than using Cookies to store user information.

Step 1. Create an Asp.Net Web API application

To keep the project as simple as possible, I started off with creating an Asp.Net web application using theEmpty Project template in Visual studio new project wizard.

Step 2. Install dependency packages

Install following packages using Nuget package manager, you could either search them in NuGet package manager or run the following commands in NuGet Package manager console
install-package Microsoft.Owin.Host.SystemWeb
install-package Microsoft.Owin.Security
install-package Microsoft.Owin.Security.OAuth
install-package Microsoft.AspNet.WebApi.Owin

Step3. Create Startup class

The classStartup replaced the good old Global.asax & Global.asax.cs files when building a web application with Katana / owin. Create a new class in the project, name it Startup. Modify the contents as given below.

using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Web.Http;

namespace AspNetJwt
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/oauth/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
                Provider = new CustomOAuthProvider()
            };
            app.UseOAuthAuthorizationServer(OAuthServerOptions);

            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

            HttpConfiguration config = new HttpConfiguration();
            config.Routes.MapHttpRoute(
                name: "Default Api",
                routeTemplate: "api/{controller}/{id}",
                defaults: new {
                    controller = "Home",
                    id = RouteParameter.Optional
                }
            );
            app.UseWebApi(config);
        }
    }
}

Step 4. Implement IOAuthAuthorizationServerProvider

Create a new class int the project and call it CustomOAuthProvider.cs. Inherit this class from IOAuthAuthorizationServerProvider. The IOAuthAuthorizationServerProvider contains the methods that will be triggered when web application receives an Access token request.
When an access token request reaches ASP.Net web api, it executes the following functions in the same order.

  • MatchEndPoint
  • ValidateClientAuthentication
  • ValidateTokenRequest
  • GrantResourceOwnerCredentials
  • TokenEndpoint
  • TokenEndpointResponse
MatchEndPoint

This function is triggered for every Http Request, it is used for identifying if the current request is for an Access token” or for an “API” or Resource.

ValidateClientAuthentication

This function is called to identify if the origin of the HTTP request is a valid client of the web application. If the requestor has provided a client id in the request, you can retrieve it using.context.clientId To keep this example as simple as possible, I have bypassed the validation logic just by validating all token requests.
Example http post request that has client id
grant_type=password&username=shibu&password=password&client_id=myclientid&client_secret=myclientsecret

ValidateTokenRequest

This method is called for every request to Token endpoint to validate the token request.

GrantResourceOwnerCredentials

This method is called for every request to Token endpoint, when grant type is Password. This method is responsible for validating user credentials and creating claims that are relevant to the user. You can access user name by calling context.UserName and password by calling context.Password. There are different types of grant types, for the simplcity of this article, I’m only discussing “grant_type=password” in this article.

You can read more about the OAuth 2.0 flows in this link

TokenEndpoint

Is called at the final stage of a successful Token endpoint request. An application may implement this call in order to do any final modification of the claims being used to issue access or refresh tokens. You can use this method to add additonal information token response.

TokenEndpointResponse

This function is called before the Token response is send to caller.

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace AspNetJwt
{
    public class CustomOAuthProvider : IOAuthAuthorizationServerProvider
    {
        public Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
        {
            throw new NotImplementedException();
        }

        public Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
        {
            throw new NotImplementedException();
        }

        public Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
        {
            throw new NotImplementedException();
        }

        public Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
        {
            throw new NotImplementedException();
        }

        public Task GrantCustomExtension(OAuthGrantCustomExtensionContext context)
        {
            throw new NotImplementedException();
        }

        public Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        {
            throw new NotImplementedException();
        }

        public Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            string userName = context.UserName;
            string password = context.Password;

            if(userName != "shibu" && password != "password")
            {
                context.SetError("Invalid grant", "Provided username and password are incorrect");
            }

            ClaimsIdentity identity = new ClaimsIdentity("JWT");

            identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            identity.AddClaim(new Claim("sub", context.UserName));
            identity.AddClaim(new Claim(ClaimTypes.Role, "Architect"));

            AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>()
            {
                {
                    "audience", (context.ClientId ==null) ? string.Empty : context.ClientId
                }
            });

            AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
            context.Validated(ticket);

            return Task.FromResult<object>(null);

        }

        public Task MatchEndpoint(OAuthMatchEndpointContext context)
        {
            return Task.FromResult<object>(null);
        }

        public Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            return Task.FromResult<object>(null);
        }

        public Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
        {
            return Task.FromResult<object>(null);
        }

        public Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
        {
            throw new NotImplementedException();
        }

        public Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }

        public Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            throw new NotImplementedException();
        }

        public Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }
    }
}

Step 5. Implement a controller

Create a file in the project and call it HomeController.cs. Modify the contents of this file as mentioned below. The home controller is a Web API controller class, it contains two GET methods. one of the GET methods is protected by [Authorize] which mean, it can only be accessed by users who are authorized by the system.

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

namespace AspNetJwt
{
    public class HomeController:ApiController
    {
        public IEnumerable<string> Get()
        {
            return new string[] { "value 1", "value 2" };
        }

        [Authorize]
        public string Get(int id)
        {
            return "value";
        }
    }
}

Step 6. Build and Run the application

We have done enough modifications to the program, you can clean, build, and run the application. Use a HTTP client like Postman (link) or Fiddler (link) to send http requests to the application.

When you run the application, you will receive a Http 404 or similar error, please ignore this error; we are not going use the browser for this sample app.

Http request – Request access token

using your choice of http proxy / client, send the below request into the web applciation to request an access token.

POST http://localhost:55882/oauth/token HTTP/1.1
User-Agent: Fiddler
Host: localhost:55882
Content-Length: 52

grant_type=password&username=shibu&password=password
Response

The response will contain the access token. Copy this value and save it for next requests.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Expires: -1
Server: Microsoft-IIS/10.0
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc3RoYW5uaWtrdW5uYXRoXHNvdXJjZVxyZXBvc1xBc3BOZXRKd3RcQXNwTmV0Snd0XG9hdXRoXHRva2Vu?=
X-Powered-By: ASP.NET
Date: Mon, 23 Oct 2017 15:53:30 GMT
Content-Length: 378

{"access_token":"w4v-jiPyARCagsIK2h7RceLLnS4c99_y8PxGo4ksIqOIJkLlUkoGjxjndcLP7pF88NPC_VY_vv4tTk6gOuOXF8Xtm1gooW6tG2cgF-oXPvmawe97GzIm_ORMpWLueTVnWRXHQQMWzZwiLv7NhwkSsBt7vAwhHvKk1jAkR1PFkXKdVAn6JkWomCcm25bxixm1Q4HrQiGgqKPKMSoEBs8MsCrbBI5rfdQoLNPQ0Jcgl3CiH2b6VtQwrPsblzGU5pDsjYIPt2pu3kQzBZ5Yj6TCJE91ySSUrFe8wB6wDtUb2XNZFkkNTa-k-nEeMUJZOW5B","token_type":"bearer","expires_in":599}

Http request – Resource request

In this request we are going to use the generated authorization token and request a protected resourse or api. using tyour http client send the below request into application.

GET http://localhost:55882/api/home/1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:55882
Content-Length: 0
Authorization: Bearer w4v-jiPyARCagsIK2h7RceLLnS4c99_y8PxGo4ksIqOIJkLlUkoGjxjndcLP7pF88NPC_VY_vv4tTk6gOuOXF8Xtm1gooW6tG2cgF-oXPvmawe97GzIm_ORMpWLueTVnWRXHQQMWzZwiLv7NhwkSsBt7vAwhHvKk1jAkR1PFkXKdVAn6JkWomCcm25bxixm1Q4HrQiGgqKPKMSoEBs8MsCrbBI5rfdQoLNPQ0Jcgl3CiH2b6VtQwrPsblzGU5pDsjYIPt2pu3kQzBZ5Yj6TCJE91ySSUrFe8wB6wDtUb2XNZFkkNTa-k-nEeMUJZOW5B
Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc3RoYW5uaWtrdW5uYXRoXHNvdXJjZVxyZXBvc1xBc3BOZXRKd3RcQXNwTmV0Snd0XGFwaVxob21lXDE=?=
X-Powered-By: ASP.NET
Date: Mon, 23 Oct 2017 15:55:05 GMT
Content-Length: 7

"value"

Http request – Request protected resource without authorization header

In this request, we will verify that when a request is sent without proper Authorization token int he header, access to the resource is denied. Using your HTTP client, send below HTTP request into web application

GET http://localhost:55882/api/home/1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:55882
Content-Length: 0
Response
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
WWW-Authenticate: Bearer
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc3RoYW5uaWtrdW5uYXRoXHNvdXJjZVxyZXBvc1xBc3BOZXRKd3RcQXNwTmV0Snd0XGFwaVxob21lXDE=?=
X-Powered-By: ASP.NET
Date: Mon, 23 Oct 2017 15:54:27 GMT
Content-Length: 61

{"Message":"Authorization has been denied for this request."}

Http request – Request un-protected resource without authorization header

If you request an unprotected api (without [Authorize] filter), you will get the response as expected.