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 certain views.
Prerequisites
Before reading this article, you must have some basic knowledge about AngularJS and Token-based 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 the NuGet package manager.
PM> Install-Package angularjs
Preceding command includes all available AngularJS libraries including minified version. So delete script files that are not required.
UI Bootstrap
We can download the latest Bootstrap version using the 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 a project using the empty project template.
In this project structure, I have created a folder named "Modules", this contains all AngularJS application files and resources files and the "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 applications and modules (modules are nothing but a collection of services, directives, filters which are used by the application). Each module has a 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 a server endpoint (web API). It also captures all response and response errors.
- 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 a container for the application. It will contain the navigation menus which contains all the available links for the application. It also contains a 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 an index controller under the "Controller" folder which will respond to change the layout of for index page i.e. when a user is not logged in, it displays only the "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 into client windows session, remove a token from the client windows session, and set HTTP header. We have to configure the HTTP request header for the endpoint: content type as “application/x-www-form-urlencoded” and sent the data as a 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 an HTTP request to the endpoint with user credential and the endpoint will validate the user credential and generate a token. This method also set an authorized token to the browser window session. The function "logout" is responsible to clear browser window session and redirect to the 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, the controller is simple and will contain 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 the Home controller and its view
Lastly, I have added a home controller and its view. It has a very simple view and an empty controller which is used to display the text "Home page".
HomeController.js
- (function () {
- 'use strict';
- app.controller('homeController', ['$scope', function ($scope) {
-
- }]);
- })();
Summary
Now we have a SPA that authenticates users by using a 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 the important HTML and script code within this article. The attached code contains the whole project, please take it as reference.