Introduction
In my previous article, we saw an overview of Token based authentication using ASP.net web API and OWIN. In this post, I will explain how to use Token based authentication in AngularJS.
In this post, I will build sample Single Page Application (SPA) using AngularJS and this application will do the following:
- Allow user to login using "Admin" and "Jignesh" user ID
- Token keep alive 30 minutes
- Authenticated user will access the certain views.
Prerequisites
Before reading this article, you must have some basic knowledge about AngularJS and Token base authentication using OWIN.
The following are the steps to create AngularJS Token Authentication using ASP.NET Web API 2 and OWIN
Step 1
Include 3rd party libraries
To get started, we required to include the following libraries:
AngularJS We can download the latest AngularJS version using NuGet package manager.
PM> Install-Package angularjs Preceding command includes all available AngularJS libraries including minified version. So delete script files which are not required.
UI Bootstrap We can download the latest Bootstrap version using NuGet package manager.
PM> Install-Package bootstrap -Version 3.3.5 Step 2
Organize Project Structure We can use any IDE to build the web application because this web app totally decouples with backend API and it develops using HTML, AngularJS, and CSS. Here I am using Visual Studio 2013. I have created project using empty project template.
In this project structure, I have created a folder named "Modules", this contains all AngularJS application files and resources files and "Asset" folder contains the asset of this project i.e. AngularJS libraries file, CSS files, etc.
Step 3 Boot Strapping Angular Application
Boot Strapping Angular Application means creating angular application and modules (modules are nothing but collection of services, directives, filters which are used by the application). Each module has configuration block and applied to the application during this process. To do this, I have added a file called "app.js" in the root folder. This file also contains the route and interceptor.
Interceptor is a regular service and it allows us to capture every XHR request and we can also manipulate it before sending it to server end point (web API). It also captures all response and response error.
- var serviceBase = 'http://localhost:49707/';
-
- var app = angular.module('AngularApp', ['ngRoute', 'LocalStorageModule']);
-
- app.config(function ($routeProvider) {
-
- $routeProvider.when("/home", {
- controller: "homeController",
- templateUrl: "/Modules/views/home.html"
- });
-
- $routeProvider.when("/login", {
- controller: "loginController",
- templateUrl: "/Modules/views/login.html"
- });
- $routeProvider.when("/next", {
- controller: "nextController",
- templateUrl: "/Modules/views/Next.html"
- });
- $routeProvider.when("/myInfo", {
- templateUrl: "/Modules/views/Info.html"
- });
-
- $routeProvider.otherwise({ redirectTo: "/home" });
-
- })
- .config(['$httpProvider', function ($httpProvider) {
-
- $httpProvider.interceptors.push(function ($q, $rootScope, $window, $location) {
-
- return {
- request: function (config) {
-
- return config;
- },
- requestError: function (rejection) {
-
- return $q.reject(rejection);
- },
- response: function (response) {
- if (response.status == "401") {
- $location.path('/login');
- }
-
- return response;
- },
- responseError: function (rejection) {
-
- if (rejection.status == "401") {
- $location.path('/login');
- }
- return $q.reject(rejection);
- }
- };
- });
- }]);
Here, I have defined four views with their corresponding controllers
- Home: It is home page. It can be also access by the anonymous users.
- Login: It shows the login form. It can be also access by the anonymous users.
- next: It shows after user has been logged-in.
- myInfo: It shows my details.
Step 4
Add Index.html (Shell Page)
Single page application contains the Shell page which is container for the application. It will contain the navigation menus which contains all the available links for the application. It also contains reference of all the 3rd party JavaScript and CSS files which are required by the application.
- <!DOCTYPE html>
- <html data-ng-app="AngularApp">
- <head>
- <meta content="IE=edge, chrome=1" http-equiv="X-UA-Compatible" />
- <title>AngularJS - OWIN Authentication</title>
- <link href="Asset/Content/bootstrap.min.css" rel="stylesheet" />
- <link href="Asset/Content/ProjectStyle.css" rel="stylesheet" />
- </head>
- <body>
- <div class="navbar navbar-inverse navbar-fixed-top" role="navigation" data-ng-controller="indexController">
- <div class="container">
- <div class="collapse navbar-collapse" data-collapse="!navbarExpanded">
- <ul class="nav navbar-nav navbar-right">
- <li data-ng-hide="!authentication.IsAuthenticated"><a href="#">Welcome, {{authentication.userName}}</a></li>
- <li data-ng-hide="!authentication.IsAuthenticated"><a href="#/myInfo">My Info</a></li>
- <li data-ng-hide="!authentication.IsAuthenticated"><a href="" data-ng-click="logOut()">Logout</a></li>
- <li data-ng-hide="authentication.IsAuthenticated"> <a href="#/login">Login</a></li>
- </ul>
- </div>
- </div>
- </div>
- <div class="jumbotron">
- <div class="container">
- <div class="page-header text-center">
- <h3>AngularJS Owin Authentication</h3>
- </div>
- </div>
- </div>
- <div class="container">
- <div data-ng-view="">
- </div>
- </div>
- <hr />
- <div id="footer">
- <div class="container">
- <div class="row">
- AngularJS - OAuth Bearer Token Implementation Example
- </div>
- </div>
- </div>
- <!-- 3rd party libraries -->
- <script src="Asset/Scripts/angular.js"></script>
- <script src="Asset/Scripts/angular-route.js"></script>
- <script src="Asset/Scripts/angular-local-storage.min.js"></script>
-
- <!-- Load app main script -->
- <script src="Modules/app.js"></script>
-
- <!-- Load Angular services -->
- <script src="Modules/Services/loginService.js"></script>
- <script src="Modules/Services/AuthenticationService.js"></script>
- <script src="Modules/Services/AuthData.js"></script>
- <!-- Load Angular controllers -->
-
- <script src="Modules/Controllers/indexController.js"></script>
- <script src="Modules/Controllers/homeController.js"></script>
- <script src="Modules/Controllers/loginController.js"></script>
- <script src="Modules/Controllers/nextController.js"></script>
- </body>
- </html>
Indexcontroller.js Now we need to add index controller under "Controller" folder which will responsible to change the layout of for index page i.e. when user is not logged-in, it displays only "login" menu, else displays welcome text and logout menu.
- (function () {
- 'use strict';
- app.controller('indexController', ['$scope', '$location', 'authData','LoginService', function ($scope, $location, authData, loginService) {
-
- $scope.logOut = function () {
- loginService.logOut();
- $location.path('/home');
- }
- $scope.authentication = authData.authenticationData;
- }]);
- })();
Step 5: Add AngularJS Authentication Data (Factory)
This AngularJS service will be responsible for storing the authentication values. It contains the object called "authentication", which will store two values (IsAuthenticated and username). This object will be used to change the layout of the Index page (mainly menu option).
- 'use strict';
- app.factory('authData', [ function () {
- var authDataFactory = {};
-
- var _authentication = {
- IsAuthenticated: false,
- userName: ""
- };
- authDataFactory.authenticationData = _authentication;
-
- return authDataFactory;
- }]);
Step 6:
Add AuthenticationService
This AngularJS service will be responsible for get and set token data in to client windows session, remove token from the client windows session and set http header. We have to configure the http request header for the end point: content type as “application/x-www-form-urlencoded” and sent the data as string not JSON object and also need to set Bearer token.
- (function () {
- 'use strict';
- app.service('AuthenticationService', ['$http', '$q', '$window',
- function ($http, $q, $window) {
- var tokenInfo;
-
- this.setTokenInfo = function (data) {
- tokenInfo = data;
- $window.sessionStorage["TokenInfo"] = JSON.stringify(tokenInfo);
- }
-
- this.getTokenInfo = function () {
- return tokenInfo;
- }
-
- this.removeToken = function () {
- tokenInfo = null;
- $window.sessionStorage["TokenInfo"] = null;
- }
-
- this.init = function () {
- if ($window.sessionStorage["TokenInfo"]) {
- tokenInfo = JSON.parse($window.sessionStorage["TokenInfo"]);
- }
- }
-
- this.setHeader = function (http) {
- delete http.defaults.headers.common['X-Requested-With'];
- if ((tokenInfo != undefined) && (tokenInfo.accessToken != undefined) && (tokenInfo.accessToken != null) && (tokenInfo.accessToken != "")) {
- http.defaults.headers.common['Authorization'] = 'Bearer ' + tokenInfo.accessToken;
- http.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
- }
- }
- this.validateRequest = function () {
- var url = serviceBase + 'api/home';
- var deferred = $q.defer();
- $http.get(url).then(function () {
- deferred.resolve(null);
- }, function (error) {
- deferred.reject(error);
- });
- return deferred.promise;
- }
- this.init();
- }
- ]);
- })();
Step 7
Add Login service, controller and its view
LoginService.js
Login service is added under the folder “Services”. It contains the method for login and logout. The function "login" is responsible to send HTTP request to the end point with user credential and end point will validate the user credential and generate token. This method also set authorized token to browser window session. The function "logout" is responsible to clear browser window session and redirect to home page.
- function () {
- 'use strict';
- app.service('LoginService', ['$http', '$q', 'AuthenticationService', 'authData',
- function ($http, $q, authenticationService, authData) {
- var userInfo;
- var loginServiceURL = serviceBase + 'token';
- var deviceInfo = [];
- var deferred;
-
- this.login = function (userName, password) {
- deferred = $q.defer();
- var data = "grant_type=password&username=" + userName + "&password=" + password;
- $http.post(loginServiceURL, data, {
- headers:
- { 'Content-Type': 'application/x-www-form-urlencoded' }
- }).success(function (response) {
- var o = response;
- userInfo = {
- accessToken: response.access_token,
- userName: response.userName
- };
- authenticationService.setTokenInfo(userInfo);
- authData.authenticationData.IsAuthenticated = true;
- authData.authenticationData.userName = response.userName;
- deferred.resolve(null);
- })
- .error(function (err, status) {
- authData.authenticationData.IsAuthenticated = false;
- authData.authenticationData.userName = "";
- deferred.resolve(err);
- });
- return deferred.promise;
- }
- this.logOut = function () {
- authenticationService.removeToken();
- authData.authenticationData.IsAuthenticated = false;
- authData.authenticationData.userName = "";
- }
- }
- ]);
- })();
loginController.js Now I have added logincontroller.js under the folder "Controllers". Generally controller is simple and will contain the client side business logic. It is a bridge between service and HTML view.
- (function () {
- 'use strict';
- app.controller('loginController', ['$scope', 'LoginService', '$location', function ($scope, loginService, $location) {
-
- $scope.loginData = {
- userName: "",
- password: ""
- };
-
- $scope.login = function () {
- loginService.login($scope.loginData.userName, $scope.loginData.password).then(function (response) {
- if (response != null && response.error != undefined) {
- $scope.message = response.error_description;
- }
- else {
- $location.path('/next');
- }
- });
- }
- }]);
- })();
Logincontroller responsible to redirect authenticated users only to the "next" view else system will redirect to login page. To do this we need to write some code in controller side and catch the "401" response code at interceptor. I have defined interceptor in app.js file. AuthenticationService has method called "validateRequest", it help us to validate whether user is logged-in or not by sending request to server and server will sent "401" status code if it is unauthorized. Here I have added one example code in "nextController".
nextController.js- (function ()
- {
- 'use strict';
- app.controller('nextController', ['$scope', 'AuthenticationService', function ($scope, authenticationService) {
- authenticationService.validateRequest();
- }]);
- })();
Login.html
View for the log-in is very simple. A new file named “login.html” created under the view folder.
- <form role="form">
- <div class="row">
- <div class="col-md-2">
-
- </div>
- <div class="col-md-4">
- <h2 class="form-login-heading col-md-12">Login</h2>
- <div class="col-md-12 PaddingTop">
- <input type="text" class="form-control" placeholder="Username" data-ng-model="loginData.userName" required autofocus>
- </div>
- <div class="col-md-12 PaddingTop">
- <input type="password" class="form-control" placeholder="Password" data-ng-model="loginData.password" required>
- </div>
- <div class="col-md-12 PaddingTop">
- <button class="btn btn-md btn-info btn-block" type="submit" data-ng-click="login()">Login</button>
- </div>
- <div data-ng-hide="message == ''">
- {{message}}
- </div>
- </div>
-
- <div class="col-md-2">
-
- </div>
- </div>
- </form>
Step 8
Add Home controller and its view
Lastly, I have added home controller and its view. It has very simple view and empty controller which is used to display text "Home page".
HomeController.js- (function () {
- 'use strict';
- app.controller('homeController', ['$scope', function ($scope) {
-
- }]);
- })();
Summary
Now we have SPA which authenticates users by using token based approach. Here redirection for anonymous users to the login page is done by client side code. So it is very important to secure all server side (web API) methods. This is not done in this article.
Note I have tried to include all important html and script code within this article. Attached code contain whole project, please take it as reference.