Tuesday, August 8, 2017

Consuming a secured JWT webapi using ionic, angular 2

I had a requirement to create an app which consumes a secured JWT webapi using ionic and angular 2. This is how I did it. Hope you like.

Reference : http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/

1) Open Startup.cs and paste the following code

using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler.Encoder;
using Microsoft.Owin.Security.Jwt;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Http;

[assembly: OwinStartup(typeof(SecuredWebAPI.Startup))]
namespace SecuredWebAPI
{
    public class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public static string PublicClientId { get; private set; }
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();
            ConfigureOAuth(app);
            ConfigureOAuthTokenConsumption(app);

          
            WebApiConfig.Register(config);
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            app.UseWebApi(config);
        }

        private void ConfigureOAuthTokenConsumption(IAppBuilder app)
        {
            //Hosting url
            var issuer = "http://localhost:53726";
            string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
            byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);

            // Api controllers with an [Authorize] attribute will be validated with JWT
            app.UseJwtBearerAuthentication(
                new JwtBearerAuthenticationOptions
                {
                    AuthenticationMode = AuthenticationMode.Active,
                    AllowedAudiences = new[] { audienceId },
                    IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                    {
                        new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                    }
                });
        }
        public void ConfigureOAuth(IAppBuilder app)
        {
                   

            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                //For Dev enviroment only (on production should be AllowInsecureHttp = false)
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = new SimpleAuthorizationServerProvider(),
                AccessTokenFormat = new CustomJwtFormat("http://localhost:53726") //Hosing Url
            };

            // OAuth 2.0 Bearer Access Token Generation
            app.UseOAuthAuthorizationServer(OAuthServerOptions);

        }
    }
}


2) Create CustomJwtFormat.cs and paste the following code

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler.Encoder;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Web;
using Thinktecture.IdentityModel.Tokens;

namespace SecuredWebAPI
{
    public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
    {

        private readonly string _issuer = string.Empty;
        public CustomJwtFormat(string issuer)
        {
            _issuer = issuer;
        }

        public string Protect(AuthenticationTicket data)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }

            string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
            string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
            var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
            var signingKey = new HmacSigningCredentials(keyByteArray);
            var issued = data.Properties.IssuedUtc;
            var expires = data.Properties.ExpiresUtc;
            var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
            var handler = new JwtSecurityTokenHandler();
            var jwt = handler.WriteToken(token);
            return jwt;
        }

        public AuthenticationTicket Unprotect(string protectedText)
        {
            throw new NotImplementedException();
        }
    }
}

3) Open web.config and add the following app settings

<!-- You can modify these if you want -->
 <add key="as:AudienceId" value="8866" />
  <add key="as:AudienceSecret" value="U2gxMjM0NTY3ODkwMjEyMTIxMjFyZXJlcmVycmVxdGg=" />


Now we will look at the client to consume this web api, I am not going to include all the angular 2 code here. I will just add the service and login codes. Remember you need 
angular2-jwt to consume this web api through generated access token.

1) Security.service.ts 

import { Injectable } from '@angular/core';
import {Http,Headers} from '@angular/http';
import { AppSettings } from '../app.settings';
import { LoginData,CurrentUserInfo } from '../app.common';
import {Observable} from 'rxjs/Observable';
import { AuthHttp } from 'angular2-jwt';

@Injectable()
export class SecurityService {
public currentUserInfo:CurrentUserInfo;
constructor(private http: Http,private authHttp:AuthHttp) {
this.currentUserInfo = new CurrentUserInfo();
}

login(loginData:LoginData) : any {
var headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');

var credentials = "grant_type=password"
+ "&userName=" + loginData.userName
+ "&password=" + loginData.password;


return this.http.post(AppSettings.API_ENDPOINT +'/token', credentials, { headers: headers });



}

}

2) Login.ts 

import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SecurityService } from '../../app/services/security.service'
import { LoginData, CurrentUserInfo } from '../../app/app.common'
import { AuthHttp,JwtHelper } from 'angular2-jwt';
import { AppSettings } from '../../app/app.settings';


@Component({
selector:'login',
templateUrl:'login.html',
providers: [SecurityService]
})

export class LoginPage {

private message:string;
private loginData:LoginData;


constructor(private secService:SecurityService,private authHttp:AuthHttp){
this.loginData = new LoginData();
}

login(loginData)
{
this.secService.login(loginData).subscribe((res) => {
var resObj = res.json();
if (typeof resObj.access_token != "undefined")
{
this.secService.currentUserInfo.isLoggedIn = true;
localStorage.setItem('access_token',resObj.access_token);

var jwtHelper = new JwtHelper();



localStorage.setItem('loginKey',jwtHelper.decodeToken(resObj.access_token).loginKey);

this.message = "Successfull Login";
this.authHttp.get(AppSettings.API_ENDPOINT +'/api/User/GetFunctions?loginKey='+localStorage.getItem('loginKey'))
.subscribe((res)=> {console.log(res);});
}
else
{
this.secService.currentUserInfo.isLoggedIn = false;
this.message = "login Failed!!! "+
JSON.parse(resObj._body).error_description;
}
},(error)=>{
this.secService.currentUserInfo.isLoggedIn = false;
this.message = "login Failed!!! "+ error;
});
}
}