Token Based Authentication for Web API where Legacy User Database is used

0
56

Introduction

This article gives detail explanation on how to use Token Based Authentication using OAuth and OWIN where application is using custom Database having user credentials stored in legacy format.

Background

Since many days, I was going through articles about ASP.NET Web API “token based authentication”. Almost all were using ASP.NET Identity for user management features. (Which creates ASPNET* tables to manage users, roles, groups etc).

But I was unable to find an article which will show how to use OWIN & OAuth “token based authentication” for existing database having custom User management tables. This is necessary when it comes to apply OAuth authentication to legacy systems having their own user management feature already in place.

So I am writing this article after analyzing standard template given by Visual Studio for Web API Individual Accounts authentication and tweaking it to use custom DB User tables. I have used Visual Studio 2015 for this.

Using the code

As discussed above, our problem is, how an existing database User table having UserName, Password stored, can leverage Token based Authentication using OAuth and OWIN. So our typical DB query will look like below. (I have not considered Password encryption for now. I leave it to your application logic)

Let’s start by creating a standard ASP.NET Web API project as shown below. Please select Web API option from New Project template. Note, we are using “No Authentication” option as we are going to configure it manually.

Since we want to use OAuth for authentication and Entity Framework for DB access, install below Nuget packages: (They will install their dependents automatically)

EntityFramework

Microsoft.AspNet.WebApi.Owin

Microsoft.Owin.Security

Microsoft.AspNet.Identity.Owin

Microsoft.Owin.Host.SystemWeb

Next, we need to add Entity Framework model connecting to our DB User Table. I won’t go in detail of that as it is standard procedure of adding ADO.NET Entity Data Model. Resultant EDMX file should look like as below:

Next, we need to add OWIN Startup class. It will be a partial class. Add “Startup.Auth.cs” under App_Start folder and paste below code in it:

public partial class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public static string PublicClientId { get; private set; }

                public void ConfigureAuth(IAppBuilder app)
        {
                        PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
                                AllowInsecureHttp = true
            };

                        app.UseOAuthBearerTokens(OAuthOptions);
        }
    }

Again, add “Startup.cs” class at project level and paste below code in it. Replace namespace string with your project namespace.

[assembly: OwinStartup(typeof(OAuth_Custom_DB.Startup))]
namespace OAuth_Custom_DB
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

Note : Since Startup is partial class, its namespace should be same in Startup.cs and Startup.Auth.cs

Next, we need to tell Web API application to use Bearer Authentication and not Default Host Authentication. So add below lines to WebApiConfig.cs

 config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

Next, we need to configure OAuth server and options. So add a folder named “Providers” and a class file named “ApplicationOAuthProvider.cs” under it.

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
    {
        private readonly string _publicClientId;

        public ApplicationOAuthProvider(string publicClientId)
        {
            if (publicClientId == null)
            {
                throw new ArgumentNullException("publicClientId");
            }

            _publicClientId = publicClientId;
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            

            ClaimsIdentity oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            ClaimsIdentity cookiesIdentity = new ClaimsIdentity(context.Options.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(context.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);
        }

        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
                        if (context.ClientId == null)
            {
                context.Validated();
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == _publicClientId)
            {
                Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                {
                    context.Validated();
                }
            }

            return Task.FromResult<object>(null);
        }

        public static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
            {
                { "userName", userName }
            };
            return new AuthenticationProperties(data);
        }
    }

Actually it is the same file which gets generated when we select “Individual Accounts” option in Authentication mode while selecting Web API Project template. The only difference is we are changing the implementation of “GrantResourceOwnerCredentials” method. Code inside the method should look like below:



            ClaimsIdentity oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            ClaimsIdentity cookiesIdentity = new ClaimsIdentity(context.Options.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(context.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);

Last thing, apply normal [Authorize] attribute to ValuesController controller for testing purpose.

That’s it… !!!

Our application is ready to be tested for Token based authentication having a custom user ID/Password table.

Run the application so that API service starts running and ready to be consumed. Your web page should look like below except the port number after locahost:

Now let’s test standard Web API URL directly if it works without authorization.

Open Chrome Postman and type GET URL for Values Controller. Something like http://localhost:56889/api/Values It gives Authorization denied error as seen below, obviously because there is no authorization done.

So now we will obtain a “Bearer Token” after authenticating user credentials. So our Token URL will as follows: http://localhost:56889/Token. Note, “/token” is the path specified as “TokenEndPoint” in “ConfigureAuth” method of Startup class. So if it is configured as “/auth/token”, complete URL would be http://localhost:56889/Auth/Token likewise..

If you look at the Postman entries, for Token URL, we are passing three parameters with content-type as “x-www-form-urlencoded”. One of the parameter is “grant_type” and its value is “password”. This tells OAuth that user wants token to be issued.

After successful execution of the POST url, we get token issued as below.

What happened here is, this URL has called GrantResourceOwnerCredentials method. In this method we have written our custom code of User authentication using Entity Framework. If it succeeds, it executes TokenEndpoint method further to issue a bearer token.

Take a note of this token value and re-visit the GET URL of ValuesController. This time, we will be adding a header named “Authorization” and paste token value we have got after authentication as its value with pre-fixing it with “bearer “.

Now this token will be valid till the time we have specified again in OAuthAuthorizationServerOptions in ConfigureAuth method in Startup class.

Points of Interest

History

LEAVE A REPLY