Angular JS Token Based Authentication using Asp.net Core Web API 2.0 and JSON Web Token
ASP.NET Core Identity is designed to enable us to easily use a number of different storage providers for our ASP.NET applications. We can use the supplied identity providers that are included with the .NET Framework, or we can implement our own providers.
In this tutorial, we will build a Token-Based Authentication using ASP.NET Core Identity , ASP.NET Core Web API and Angular JS
With Token-Based Authentication, the client application is not dependent on a specific authentication mechanism. The token is generated by the server and the Web API have some APIs to understand, validate the token and perform the authentication. This approach provides Loose Coupling between client and the Web API.
this toturial is not for beginners, to follow it, you must understand Angular2 and Asp.NET REST Services
Securing our web application consists of two scenarios : Authentication and Authorization
1. Authentication identifies the user. So the user must be registered first, using login and password or third party logins like Facebook, Twitter, etc…
2. Authorization talks about permission for authenticated users
– What is the user (authenticated) allowed to do ?
– What ressources can the user access ?
We have build our back end service using ASP.NET WEB API Core, web api provides an internal authorization server using OWIN MIDDLEWARE
The authorization server and the authentication filter are both called into an OWIN middleware component that handles OAuth2
This article is the third part of a series of 4 articles
- Token Based Authentication using Asp.net Core Web Api
- Asp.Net Core Web Api Integration testing using EntityFrameworkCore LocalDb and XUnit2
- Angular Token Based Authentication using Asp.net Core Web API and JSON Web Token
- Angular JS Token based Authentication using Asp.net Core Web API and JSON Web Token
BUILDING WEB API RESSOURCE SERVER AND AUTHORIZATION SERVER
In the first part Token Based Authentication using Asp.net Core Web API, I talked about how to configure an ASP.NET Web API Core Token Based Authentication using JWT. So in this tutorial I will talk about an Angular2 client that connect to the Web Api Authorization server using a JWT Token
BUILDING ANGULAR2 WEB CLIENT
Create an ASP.NET Empty WebSite and structure it as follow
Package.config
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="AngularJS.Core" version="1.6.5" targetFramework="net461" /> <package id="AngularJS.Resource" version="1.6.5" targetFramework="net461" /> <package id="AngularJS.Route" version="1.6.5" targetFramework="net461" /> <package id="bootstrap" version="3.3.7" targetFramework="net461" /> <package id="jQuery" version="1.9.1" targetFramework="net461" /> </packages>
Package.config defines some dependencies , so use nuget package manager to restore them
index.html
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <link href="Content/bootstrap.css" rel="stylesheet" /> <link href="Content/Site.css" rel="stylesheet" /> </head> <body ng-app="appmodule" ng-controller="userCtrl as vm"> <div> <nav class="navbar navbar-default "> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="https://logcorner.com">LogCorner.com</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li ng-if="vm.isLoggedIn()"> <a>Welcome {{ vm.userName }}</a> </li> <li ng-if="vm.isLoggedIn()"> <span> <button type="submit" class="btn btn-default" ng-click="vm.logout()"> Log Out </button> </span> </li> </ul> </div> <div> </div> </div> </nav> <div class="container"> <div class="row"> <div class="jumbotron"> <h1>AngularJS Token Authentication </h1> <p> AngularJS Token based Authentication using Asp.net Core Web API and JSON Web Token </p> </div> </div> <div class="row"> <div ng-if="vm.isLoggedIn() == true" ng-include="'app/product/productListView.html'"> </div> <div ng-if="vm.isLoggedIn() == false" ng-include="'app/user/userLoginView.html'"> </div> </div> </div> </div> <script src="Scripts/angular.js"></script> <script src="Scripts/angular-resource.js"></script> <script src="app/app.js"></script> <script src="app/Common/common.services.js"></script> <script src="app/user/userCtrl.js"></script> <script src="app/product/productListCtrl.js"></script> <script src="app/service/loginservice.js"></script> <script src="app/service/productService.js"></script> <script src="app/Model/userProfile.js"></script> </body> </html>
lets include *.css files on header section
<link href="Content/bootstrap.css" rel="stylesheet" /> <link href="Content/Site.css" rel="stylesheet" />
lets include *.js files at the end of body section
<script src="Scripts/angular.js"></script> <script src="Scripts/angular-resource.js"></script> <script src="app/app.js"></script> <script src="app/Common/common.services.js"></script> <script src="app/user/userCtrl.js"></script> <script src="app/product/productListCtrl.js"></script> <script src="app/service/loginservice.js"></script> <script src="app/service/productService.js"></script> <script src="app/Model/userProfile.js"></script>
Index.html is the entry point of our SPA application, Use the directive ng-app to auto-bootstrap our AngularJS application.
We can specify an AngularJS module to be used as the root module for the application. This module will be loaded into the $injector
when the application is bootstrapped. It should contain the application code needed or have dependencies on other modules that will contain the code.
Our root angular module is appmodule, we specify it in index.html by <body ng-app=”appmodule”…….
app.js
(function () { "use strict"; var app = angular.module("appmodule", ["common.services"]); }());
In app.js file we inject common.services as a dependency
common.service.js
Lets create a common.service.js file
(function () { "use strict"; angular .module("common.services", ["ngResource"]) .constant("appSettings", { serverPath: "http://localhost:58834" }); }());
Here, we implement common functionalities such as error handling, serverPath to call API, (http://localhost:58834/api is the API endpoint)
userCtrl.js
(function () { "use strict"; angular .module("appmodule") .controller("userCtrl", ["$scope", "loginservice", "userProfile", userCtrl]); function userCtrl($scope, loginservice, userProfile) { var vm = this; vm.registerErrorData = ""; vm.loginErrorData = ""; vm.responseData = ""; vm.userName = ""; vm.userEmail = ""; vm.userPassword = ""; vm.newUserEmail = ""; vm.newUserPassword = ""; vm.confirmUserPassword = ""; vm.accessToken = ""; vm.refreshToken = ""; vm.isLoggedIn = function () { var result = userProfile.getProfile().isLoggedIn; return result; }; vm.registerUser = function () { vm.responseData = ''; vm.registerErrorData = ''; vm.loginErrorData = ''; var userRegistrationInfo = { Email: vm.newUserEmail, Password: vm.newUserPassword, ConfirmPassword: vm.confirmUserPassword }; var registerResult = loginservice.register(userRegistrationInfo); registerResult.then(function (data) { vm.responseData = "User Registration successful"; vm.newUserPassword = ""; vm.confirmUserPassword = ""; }, function (response) { vm.registerErrorData = response.statusText + "\r\n"; if (response.data) { for (var key in response.data) { vm.registerErrorData += response.data[key] + "\r\n"; } if (response.data.exceptionMessage) vm.registerErrorData += response.data.exceptionMessage; } }); }; vm.redirect = function (url) { window.location.href = url; }; vm.login = function () { var userLogin = { grant_type: 'password', username: vm.userEmail, password: vm.userPassword }; vm.responseData = ''; vm.registerErrorData = ''; vm.loginErrorData = ''; var loginResult = loginservice.login(userLogin); loginResult.then(function (resp) { var result = resp.data.claims.filter(function (o) { return o.type == 'sub'; }); vm.userName = result ? result[0].value : null; userProfile.setProfile(resp.data); }, function (response) { vm.loginErrorData = response.statusText + " : \r\n"; if (response.data) { for (var key in response.data) { vm.loginErrorData += response.data[key] + "\r\n"; } } }); }; vm.logout = function () { userProfile.logout(); }; } })();
In userCtrl.js file Ininject login.services and userProfile as a dependencies: userProfile to store user data and login.service to call api
I define some functions :
- vm.isLoggedIn : to verify if the user is logged in : sessionStorage.getItem(‘accessToken’) != null
- vm.registerUser : register a new user. it uses loginservice.register
- vm.login : log in user. it uses loginservice.login
- vm.logout : logout user. it uses userProfile to clear user data
in index.html view I reference userCtrl as ng-controller=”userCtrl as vm”
So if user is loggedIn I display userLoginView
<div ng-if="vm.isLoggedIn() == false" ng-include="'app/user/userLoginView.html'"> </div>
else I display productListView
<div ng-if="vm.isLoggedIn() == false" ng-include="'app/user/userLoginView.html'"> </div>
userLoginView.html
<div class="row"> <div class="col-md-6"> <div class="panel panel-success"> <div class="panel-heading center-block"> <span>REGISTER A NEW USER</span> </div> <div class="panel-body"> <form ng-hide="vm.isLoggedIn()" class="form-horizontal" role="form"> <div class="form-group"> <input type="text" class="form-control" placeholder="username (email)" ng-model="vm.newUserEmail"> </div> <div class="form-group"> <input type="password" class="form-control col-md-10" placeholder="password" ng-model="vm.newUserPassword"> </div> <div class="form-group"> <input type="password" class="form-control col-md-10" placeholder="ConfirmPassword" ng-model="vm.confirmUserPassword"> </div> <button type="submit" class="btn btn-default" ng-click="vm.registerUser()"> Register </button> </form> </div> <div class="panel-footer"> <div class="alert-danger" ng-show="vm.registerErrorData">{{vm.registerErrorData}}</div> <div class="alert-success" ng-show="vm.responseData">{{vm.responseData}}</div> </div> </div> </div> <div class="col-md-6"> <div class="panel panel-success"> <div class="panel-heading center-block"> <span>LOGIN IN</span> </div> <div class="panel-body"> <form ng-hide="vm.isLoggedIn()" class="form-horizontal" role="form"> <div class="form-group"> <input type="text" class="form-control col-md-10" placeholder="username (email)" ng-model="vm.userEmail"> </div> <div class="form-group"> <input type="password" class="form-control col-md-10" placeholder="password" ng-model="vm.userPassword"> </div> <button type="submit" class="btn btn-default" ng-click="vm.login()"> Login </button> </form> </div> <div class="panel-footer"> <div class="alert-danger" ng-show="vm.loginErrorData">{{vm.loginErrorData}}</div> </div> </div> </div> </div>
UserLoginView defines the login form and the register form and uses userCtrl :
- ng-click=”vm.registerUser()” handles registerUser function of userCtrl.
- ng-click=”vm.login()” handles login function of userCtrl.
<button type="submit" class="btn btn-default" ng-click="vm.registerUser()"> Register </button> ...................... <button type="submit" class="btn btn-default" ng-click="vm.login()"> Login </button>
loginService.js
(function () { "use strict"; angular .module("common.services") .factory("loginservice", ["$http", "appSettings", loginservice]) function loginservice($http, appSettings) { this.register = function (userInfo) { var resp = $http({ url: appSettings.serverPath + "/api/auth/Register", method: "POST", data: userInfo, }); return resp; }; this.login = function (userlogin) { var contentHeaders = [{ 'Content-Type': 'application/json' }, { 'Accept': 'application/json' }, { 'Content-Type': 'application/x-www-form-urlencoded' } ] var credentials = { grant_type: 'password', email: userlogin.username, password: userlogin.password }; var resp = $http({ url: appSettings.serverPath + "/api/auth/token", method: "POST", data: credentials, headers: contentHeaders, }); return resp; }; return { register: this.register, login: this.login } } })();
- register function : register a new to database
- login function : log an existing user and returns a valid token
userProfile.js
(function () { "use strict"; angular .module("common.services") .factory("userProfile", userProfile) function userProfile() { var setProfile = function (data) { sessionStorage.setItem('accessToken', data.token); sessionStorage.setItem('claims', data.claims); sessionStorage.setItem('expiration', data.expiration); }; var getProfile = function () { var profile = { isLoggedIn: sessionStorage.getItem('accessToken') != null, token: sessionStorage.getItem('accessToken'), claims: sessionStorage.getItem('claims'), expiration: sessionStorage.getItem('expiration') }; return profile; }; var getToken = function () { return sessionStorage.getItem('accessToken'); }; var getAuthHeaders = function () { var accesstoken = sessionStorage.getItem('accessToken'); var authHeaders = {}; if (accesstoken) { authHeaders.Authorization = 'Bearer ' + accesstoken; } return authHeaders; }; var logout = function () { sessionStorage.removeItem('accessToken'); }; return { setProfile: setProfile, getProfile: getProfile, getToken: getToken, getAuthHeaders: getAuthHeaders, logout: logout } } })();
userProfile : after succesfull login , store user informations like userName, token and claims to sessionStorage.
If the user logout, I clear user informations from session
ProductListCtrl.js
(function () { "use strict"; angular .module("appmodule") .controller("ProductListCtrl", ["$scope", "productService", ProductListCtrl]); function ProductListCtrl($scope, productService) { var vm = this; vm.products = []; vm.Message = ""; GetProducts(); function GetProducts() { var groupResult = productService.get(); groupResult.then(function (resp) { vm.products = resp.data; vm.Message = "Call Completed Successfully"; }, function (err) { vm.Message = "Error!!! " + err.status }); }; } }());
productService.js
(function () { "use strict"; angular .module("common.services") .factory("productService", ["$http", "appSettings", "userProfile", productService]) function productService($http, appSettings, userProfile) { this.get = function () { var authHeaders = userProfile.getAuthHeaders(); var response = $http({ url: appSettings.serverPath + "/api/product", method: "GET", headers: authHeaders }); return response; }; return { get: this.get } } })();
To get products, user must be logged in first, so user must retrive a token, and send the token with the getProduct request
var authHeaders = userProfile.getAuthHeaders();
var response = $http({
url: appSettings.serverPath + “/api/product”,
method: “GET”,
headers: authHeaders
});
productListView.html
<div class="panel panel-success" ng-controller="ProductListCtrl as vm"> <div class="panel-heading" style="font-size:large"> Product List : you must be logged in to view this list </div> <div class="panel-body"> <table class="table"> <thead> <tr> <td>Id</td> <td>Name</td> <td>Price</td> <td>Description</td> </tr> </thead> <tbody> <tr ng-repeat="product in vm.products"> <td>{{ product.id}}</td> <td>{{ product.name }}</td> <td>{{ product.price }}</td> <td>{{ product.description }}</td> </tr> </tbody> </table> </div> </div>
SEE IT IN ACTION
Open Package Manager Console and run update-database command to generate database
This is the generated database on localdb
Configure Startup multiple project : TokenAuthWebApiCore.Server and AngularJSClient
Run F5
Soure code is available here https://github.com/logcorner/Angular-JS-Token-Based-Authentication-using-Asp.net-Core-Web-API-2.0-and-JSON-Web-Token