Wednesday, 11 January 2017

Token Based Authentication using ASP.NET Web API 2 with OWIN

Basics of Token Based Authentication
As per http://www.w3.org

"The general concept behind a token-based authentication system is simple. Allow users to enter their username and password in order to obtain a token which allows them to fetch a specific resource - without using their username and password. Once their to ken has been obtained, the user can offer the token - which offers access to a specific resource for a time period - to the remote site. Using some form of authentication: a header, GET or POST request, or a cookie of some kind, the site can then determin e what level of access the request in question should be afforded."

The industry standard to achieve this goal is via OWIN (Open Web Interface for .NET). So What is OWIN ?

"OWIN defines a standard interface between .NET web servers and web applications. The goal of the OWIN interface is to decouple server and application, encourage the development of simple modules for .NET web development, and, by being an open standard, stimulate the open source ecosystem of .NET web development tools."

For further details please refer to http://owin.org/

Token Based Authentication with ASP.NET
"The ASP.NET Identity system is designed to replace the previous ASP.NET Membership and Simple Membership systems. It includes profile support, OAuth integration, works with OWIN, and is included with the ASP.NET templates shipped with Visual Studio 2013."

More details about ASP.NET Identity system can be found at http://www.asp.net/identity Lets create a sample ASP.NET Web API project that uses OWIN for token based authentication

1. Open VS2012/2013

2. File -> New -> Project -> Web -> ASP.NET Web API 2 Empty Project

3. Install following Nuget Packages
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<packages>
  <package id="Antlr" targetframework="net45" version="3.4.1.9004"></package>
  <package id="bootstrap" targetframework="net45" version="3.0.0"></package>
  <package id="EntityFramework" targetframework="net45" version="6.1.0"></package>
  <package id="jQuery" targetframework="net45" version="1.10.2"></package>
  <package id="jQuery.Validation" targetframework="net45" version="1.11.1"></package>
  <package id="Microsoft.AspNet.Cors" targetframework="net45" version="5.0.0"></package>
  <package id="Microsoft.AspNet.Identity.Core" targetframework="net45" version="2.0.1"></package>
  <package id="Microsoft.AspNet.Identity.EntityFramework" targetframework="net45" version="2.0.1"></package>
  <package id="Microsoft.AspNet.Identity.Owin" targetframework="net45" version="2.0.1"></package>
  <package id="Microsoft.AspNet.Mvc" targetframework="net45" version="5.0.0"></package>
  <package id="Microsoft.AspNet.Razor" targetframework="net45" version="3.0.0"></package>
  <package id="Microsoft.AspNet.Web.Optimization" targetframework="net45" version="1.1.1"></package>
  <package id="Microsoft.AspNet.WebApi" targetframework="net45" version="5.0.0"></package>
  <package id="Microsoft.AspNet.WebApi.Client" targetframework="net45" version="5.1.2"></package>
  <package id="Microsoft.AspNet.WebApi.Core" targetframework="net45" version="5.1.2"></package>
  <package id="Microsoft.AspNet.WebApi.Owin" targetframework="net45" version="5.1.2"></package>
  <package id="Microsoft.AspNet.WebApi.WebHost" targetframework="net45" version="5.0.0"></package>
  <package id="Microsoft.AspNet.WebPages" targetframework="net45" version="3.0.0"></package>
  <package id="Microsoft.jQuery.Unobtrusive.Validation" targetframework="net45" version="3.0.0"></package>
  <package id="Microsoft.Owin" targetframework="net45" version="2.1.0"></package>
  <package id="Microsoft.Owin.Cors" targetframework="net45" version="2.1.0"></package>
  <package id="Microsoft.Owin.Host.SystemWeb" targetframework="net45" version="2.1.0"></package>
  <package id="Microsoft.Owin.Security" targetframework="net45" version="2.1.0"></package>
  <package id="Microsoft.Owin.Security.Cookies" targetframework="net45" version="2.1.0"></package>
  <package id="Microsoft.Owin.Security.OAuth" targetframework="net45" version="2.1.0"></package>
  <package id="Microsoft.Web.Infrastructure" targetframework="net45" version="1.0.0.0"></package>
  <package id="Modernizr" targetframework="net45" version="2.6.2"></package>
  <package id="Newtonsoft.Json" targetframework="net45" version="5.0.6"></package>
  <package id="Owin" targetframework="net45" version="1.0"></package>
  <package id="WebGrease" targetframework="net45" version="1.5.2"></package>
</packages>

4. Update "WebApiConfig.cs"
- Enable attribute based routing - Enable Json formatter
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void Register(HttpConfiguration config)
{
    // Web API routes
    config.MapHttpAttributeRoutes();
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
    var jsonFormatter = config.Formatters.OfType<jsonmediatypeformatter>().First();
    jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
</jsonmediatypeformatter>

5. Add a new class "Startup.cs"
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();
        ConfigureOAuth(app);
        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.UseWebApi(config);
    }
    public void ConfigureOAuth(IAppBuilder app)
    {
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/authtoken"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new SimpleAuthorizationServerProvider()
        };
        // Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}
TokenEndpointPath = new PathString("/authtoken") defines the endpoint for your web api which will make a request to get authentication token. Also note that as per the snipped token expires with in a day.

consider for example you website is www.saifikram.com then the call to get the token would be www.saifikram.com/api/authtoken.

6. Add a new class "SimpleAuthorizationServerProvider.cs"
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
    }
    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        bool isValidUser = false;
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
         
        if(context.UserName == "test" && context.Password == "test")
        {
            isValidUser = true;
        }
         
        if (!isValidUser)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }
         
        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim("sub", context.UserName));
        identity.AddClaim(new Claim("role", "user"));
        context.Validated(identity);
    }
}
Here we are just verifying for hard coded values of username and password, you could actually implement database logic etc.

7. Add a new controller that serves authenticated requests (ie.. based on token passed) "DataController.cs"
?
1
2
3
4
5
6
7
8
9
10
11
12
[RoutePrefix("api/data")]
[Authorize]
public class DataController : ApiController
{
    [Route("")]
    public IHttpActionResult Get()
    {
        ClaimsPrincipal principal = Request.GetRequestContext().Principal as ClaimsPrincipal;
        var userName = principal.Claims.Where(c => c.Type == "sub").Single().Value;
        return Ok("You are allowed to request data");
    }
}

That's it. we have now successfully implemented token based authentication using ASP.NET WEB API 2 with OWIN.

Test Web API using Fiddler
In order to test this we could use either fiddler or browser extensions. Let use fiddler and test this.

Request a Token
1. Run your web site (ie.. you should be able to see a default page)
2. Open Fiddler

3. Goto Composer Tab

4. Select request verb as "POST"

5. Type url as http://localhost:59104/authtoken

6. Inside request body copy following text :
?
1
grant_type=password&username=test&password=test

7. Now click on "Execute" button

8. Go to response and you should see this
?
1
2
3
4
5
{
"access_token":"fGlCYARtrfm5_9Oq9HGhKAkHEP5M6GJ8AeSJe4TWlK6sewcM2ewUXahGU7LnnGxtP9Grf6g7RbUEyzkohy3GEaNe77QPg-wGf-7e4r_8vGPrO8hk0CrUrgTiQ_25f2SSjUDMoiNibfy89DtKcpP1Givjbo5TCT2FzRRf_EljGiQsDJ43wZ9szJ1eUdgePzbggrUGYp0xClFifgwwo29a6H_a8dVmuVTn_G2719rbr8M",
"token_type":"bearer",
"expires_in":86399
}

Use Token for subsequent requests
1. Open Fiddler

2. Goto Composer Tab

3. Select request verb as "GET"

4. Type url as http://localhost:59104/api/data

5. Add a new request header as follows
?
1
Authorization: Bearer fGlCYARtrfm5_9Oq9HGhKAkHEP5M6GJ8AeSJe4TWlK6sewcM2ewUXahGU7LnnGxtP9Grf6g7RbUEyzkohy3GEaNe77QPg-wGf-7e4r_8vGPrO8hk0CrUrgTiQ_25f2SSjUDMoiNibfy89DtKcpP1Givjbo5TCT2FzRRf_EljGiQsDJ43wZ9szJ1eUdgePzbggrUGYp0xClFifgwwo29a6H_a8dVmuVTn_G2719rbr8M
Authorization: Bearer 

7. Now click on "Execute" button

8. Go to response and you should see this
?
1
You are allowed to request data