Sunday, 10 February 2019

Token-Based Authentication In Angular 6 With Web API


What is Authentication?
Authentication is the process by which we check whether a user is allowed to access the application. For this we match the passed credentials in our database and if the credentials do not match, we restrict the user to access the application.

What is Token-Based Authentication?
Here we are using token based authentication to validate user in our angular application by passing the credentials to Asp.Net Web API. Web API validate the credentials and if validated then return a valid  access token with expiration time. Angular application store this access token locally in sessionStorage or localStorage. For subsequent request user no need to provide the credentials again and this access token can be used to validate user till the token is valid.

Let’s create an ASP.Net Web API first.
Open Visual Studio and Create New project using File=>New=>Project…




Select ASP.NET Web Application(.Net Framework) from Installed Web templates. Enter a name for the project “TokenAuthWebAPI” and click OK button.


Select Web API from project template. Click Change Authentication button and click OK button.


Select Individual User Accounts option and click OK. 



It will take some time to create project with selected template. Below is the application structure created automatically by Visual Studio.



Here in the Providers folder visual studio has created a ApplicationOAuthProvider class which validate the user from Microsoft Identity tables. When we apply [Authorize] attribute on any controller or action, Visual Studio create identity tables in App_Data folder. If we set our database name in DefaultConnection in connectionStrings section in web.config file, it creates those tables in our database. But we mostly require to validate user from our own user table in our database. To do that we need to override this functionality by creating our own custom Authentication provider to validate user from our user table.
So, add a class CustomOAuthProvider that implements OAuthAuthorizationServerProvider class.
We need to override GrantResourceOwnerCredentials method of this class. Here, I have used hard coded username & password to validate the user but we can write code to validate user from our user table by passing these username and password obtained from context object to Stored Procedure.

CustomOAuthProvider.cs

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

namespace TokenAuthWebAPI.Providers
{
    public class CustomOAuthProvider : OAuthAuthorizationServerProvider
    {
        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
           
            await Task.Run(() =>
            {
                string username = "pramod";
                string password = "pramod@12345";
                bool isValidUser = (context.UserName.ToLower() == username && context.Password == password) ? true : false;
                if (isValidUser == false)
                {
                    context.SetError("invalid_grant", "The user name or password is incorrect.");
                    return;
                }

                List<Claim> claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.Name, user.username)
                };
                ClaimsIdentity oAuthIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);


                ClaimsIdentity cookiesIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);

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

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

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

        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 static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
            {
                { "userName", userName }
            };
            return new AuthenticationProperties(data);
        }
    }

}


To allow CORS request, we need to install Microsoft.Owin.Cors from NuGet Package Manager



In Startup.cs file comment ConfigureAuth(app) in Configuration() method and write following code. Here, we have used our CustomOAuthProvider class instance.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security.OAuth;
using System.Web.Http;
using TokenAuthWebAPI.Providers;

[assembly: OwinStartup(typeof(TokenAuthWebAPI.Startup))]

namespace TokenAuthWebAPI
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            //ConfigureAuth(app);
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

            var customOAuthProvider = new CustomOAuthProvider();
            OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = customOAuthProvider
            };
            app.UseOAuthAuthorizationServer(options);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

            HttpConfiguration config = new HttpConfiguration();
            WebApiConfig.Register(config);
        }
    }
}

Now run the application

In Angular Application

In angular application create a service using angular-cli command
ng g service authentication --spec=false –module=app
To send http request to Web API, use HttpClient. I have injected HttpClient using constructor. HttpClient is imported from @angular/common/http library.
HttpHeaders class is used to create request headers. HttpHeaders is also imported from @angular/common/http library as follows:
import { HttpClient, HttpHeaders } from '@angular/common/http'

In login method I have called the post method of HttpClient and if the validation successful at Web API the response will contain the access token, user name and access token expiration time. We have stored user name and access token in localStorage. This will be used to check whether the user is authenticate or not for subsequent request.
Logout method remove these values from localStorage.

Authentication.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {

  API_BaseURL: string = "http://localhost:21453";

  constructor(private http: HttpClient) { }

  login(username: string, password: string) {

    let userData = "username=" + username + "&password=" + password + "&grant_type=password";
    let reqHeaders = new HttpHeaders({
      'Content-Type': 'application/x-www-form-urlencoded', 'No-Auth': 'true'
    });

    return this.http.post<any>(this.API_BaseURL + "/token", userData, { headers: reqHeaders }).pipe(map(user => {     
      localStorage.setItem("accessToken", user.access_token);
      localStorage.setItem("username", user.userName);
    }));

  }

  logout() {  
 localStorage. removeItem ("accessToken);
 localStorage. removeItem ("username);
  }

}

I hope you find this post helpful. Please comment about this post and provide any suggestion for further posts.

Thanks

No comments:

Post a Comment