This is the second part of AngularJS Token Authentication using ASP.NET Web API 2 and Owin middleware, you can find the first part using the link below:
- Token Based Authentication using ASP.NET Web API 2, Owin middleware, and ASP.NET Identity – Part 1.
- Enable OAuth Refresh Tokens in AngularJS App using ASP .NET Web API 2, and Owin – Part 3.
- ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app – Part 4.
You can check the demo application on (http://ngAuthenticationWeb.azurewebsites.net), play with the back-end API for learning purposes (http://ngauthenticationapi.azurewebsites.net), and check the source code on Github.
In this post we’ll build sample SPA using AngularJS, this application will allow the users to do the following:
- Register in our system by providing username and password.
- Secure certain views from viewing by authenticated users (Anonymous users).
- Allow registered users to log-in and keep them logged in for
24 hours30 minutes because we are using refresh tokens or until they log-out from the system, this should be done using tokens.
If you are new to AngularJS, you can check my other tutorial which provides step by step instructions on how to build SPA using AngularJS, it is important to understand the fundamentals aspects of AngularJS before start working with it, in this tutorial I’ll assume that reader have basic understanding of how AngularJS works.
Step 1: Download Third Party Libraries
To get started we need to download all libraries needed in our application:
- AngularJS: We’ll serve AngularJS from from CDN, the version is 1.2.16
- Loading Bar: We’ll use the loading bar as UI indication for every XHR request the application will made, to get this plugin we need to download it from here.
- UI Bootstrap theme: to style our application, we need to download a free bootstrap ready made theme from http://bootswatch.com/ I’ve used a theme named “Yeti”.
Step 2: Organize Project Structure
You can use your favorite IDE to build the web application, the app is completely decoupled from the back-end API, there is no dependency on any server side technology here, in my case I’m using Visual Studio 2013 so add new project named “AngularJSAuthentication.Web” to the solution we created in the previous post, the template for this project is “Empty” without any core dependencies checked.
After you add the project you can organize your project structure as the image below, I prefer to contain all the AngularJS application and resources files we’ll create in folder named “app”.
Step 3: Add the Shell Page (index.html)
Now we’ll add the “Single Page” which is a container for our application, it will contain the navigation menu and AngularJS directive for rendering different application views “pages”. After you add the “index.html” page to project root we need to reference the 3rd party JavaScript and CSS files needed as the below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
<!DOCTYPE html> <html data-ng-app="AngularAuthApp"> <head> <meta content="IE=edge, chrome=1" http-equiv="X-UA-Compatible" /> <title>AngularJS Authentication</title> <link href="content/css/bootstrap.min.css" rel="stylesheet" /> <link href="content/css/site.css" rel="stylesheet" /> <link href="content/css/loading-bar.css" rel="stylesheet" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> </head> <body> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation" data-ng-controller="indexController"> <div class="container"> <div class="navbar-header"> <button class="btn btn-success navbar-toggle" data-ng-click="navbarExpanded = !navbarExpanded"> <span class="glyphicon glyphicon-chevron-down"></span> </button> <a class="navbar-brand" href="#/">Home</a> </div> <div class="collapse navbar-collapse" data-collapse="!navbarExpanded"> <ul class="nav navbar-nav navbar-right"> <li data-ng-hide="!authentication.isAuth"><a href="#">Welcome {{authentication.userName}}</a></li> <li data-ng-hide="!authentication.isAuth"><a href="#/orders">My Orders</a></li> <li data-ng-hide="!authentication.isAuth"><a href="" data-ng-click="logOut()">Logout</a></li> <li data-ng-hide="authentication.isAuth"> <a href="#/login">Login</a></li> <li data-ng-hide="authentication.isAuth"> <a href="#/signup">Sign Up</a></li> </ul> </div> </div> </div> <div class="jumbotron"> <div class="container"> <div class="page-header text-center"> <h1>AngularJS Authentication</h1> </div> <p>This single page application is built using AngularJS, it is using OAuth bearer token authentication, ASP.NET Web API 2, OWIN middleware, and ASP.NET Identity to generate tokens and register users.</p> </div> </div> <div class="container"> <div data-ng-view=""> </div> </div> <hr /> <div id="footer"> <div class="container"> <div class="row"> <div class="col-md-6"> <p class="text-muted">Created by Taiseer Joudeh. Twitter: <a target="_blank" href="http://twitter.com/tjoudeh">@tjoudeh</a></p> </div> <div class="col-md-6"> <p class="text-muted">Taiseer Joudeh Blog: <a target="_blank" href="http://bitoftech.net">bitoftech.net</a></p> </div> </div> </div> </div> <!-- 3rd party libraries --> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-route.min.js"></script> <script src="scripts/angular-local-storage.min.js"></script> <script src="scripts/loading-bar.min.js"></script> <!-- Load app main script --> <script src="app/app.js"></script> <!-- Load services --> <script src="app/services/authInterceptorService.js"></script> <script src="app/services/authService.js"></script> <script src="app/services/ordersService.js"></script> <!-- Load controllers --> <script src="app/controllers/indexController.js"></script> <script src="app/controllers/homeController.js"></script> <script src="app/controllers/loginController.js"></script> <script src="app/controllers/signupController.js"></script> <script src="app/controllers/ordersController.js"></script> </body> </html> |
Step 4: “Booting up” our Application and Configure Routes
We’ll add file named “app.js” in the root of folder “app”, this file is responsible to create modules in applications, in our case we’ll have a single module called “AngularAuthApp”, we can consider the module as a collection of services, directives, filters which is used in the application. Each module has configuration block where it gets applied to the application during the bootstrap process.
As well we need to define and map the views with the controllers so open “app.js” file and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var app = angular.module('AngularAuthApp', ['ngRoute', 'LocalStorageModule', 'angular-loading-bar']); app.config(function ($routeProvider) { $routeProvider.when("/home", { controller: "homeController", templateUrl: "/app/views/home.html" }); $routeProvider.when("/login", { controller: "loginController", templateUrl: "/app/views/login.html" }); $routeProvider.when("/signup", { controller: "signupController", templateUrl: "/app/views/signup.html" }); $routeProvider.when("/orders", { controller: "ordersController", templateUrl: "/app/views/orders.html" }); $routeProvider.otherwise({ redirectTo: "/home" }); }); app.run(['authService', function (authService) { authService.fillAuthData(); }]); |
So far we’ve defined and mapped 4 views to their corresponding controllers as the below:
- Home view which shows the home page and can be accessed by anonymous users on http://ngauthenticationweb.azurewebsites.net/#/home
- Signup view which shows signup form and can be accessed by anonymous users on http://ngauthenticationweb.azurewebsites.net/#/signup
- Log-in view which shows log-in form and can be accessed by anonymous users on http://ngauthenticationweb.azurewebsites.net/#/login
- Orders view which shows orders forms for authenticated users only as the image below, view can be accessed on http://ngauthenticationweb.azurewebsites.net/#/orders
Step 5: Add AngularJS Authentication Service (Factory)
This AngularJS service will be responsible for signing up new users, log-in/log-out registered users, and store the generated token in client local storage so this token can be sent with each request to access secure resources on the back-end API, the code for AuthService will be as the below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
'use strict'; app.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) { var serviceBase = 'http://ngauthenticationapi.azurewebsites.net/'; var authServiceFactory = {}; var _authentication = { isAuth: false, userName : "" }; var _saveRegistration = function (registration) { _logOut(); return $http.post(serviceBase + 'api/account/register', registration).then(function (response) { return response; }); }; var _login = function (loginData) { var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password; var deferred = $q.defer(); $http.post(serviceBase + 'token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) { localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName }); _authentication.isAuth = true; _authentication.userName = loginData.userName; deferred.resolve(response); }).error(function (err, status) { _logOut(); deferred.reject(err); }); return deferred.promise; }; var _logOut = function () { localStorageService.remove('authorizationData'); _authentication.isAuth = false; _authentication.userName = ""; }; var _fillAuthData = function () { var authData = localStorageService.get('authorizationData'); if (authData) { _authentication.isAuth = true; _authentication.userName = authData.userName; } } authServiceFactory.saveRegistration = _saveRegistration; authServiceFactory.login = _login; authServiceFactory.logOut = _logOut; authServiceFactory.fillAuthData = _fillAuthData; authServiceFactory.authentication = _authentication; return authServiceFactory; }]); |
Now by looking on the method “_saveRegistration” you will notice that we are issuing HTTP Post to the end point “http://ngauthenticationapi.azurewebsites.net/api/account/register” defined in the previous post, this method returns a promise which will be resolved in the controller.
The function “_login” is responsible to send HTTP Post request to the endpoint “http://ngauthenticationapi.azurewebsites.net/token”, this endpoint will validate the credentials passed and if they are valid it will return an “access_token”. We have to store this token into persistence medium on the client so for any subsequent requests for secured resources we’ve to read this token value and send it in the “Authorization” header with the HTTP request.
Notice that we have configured the POST request for this endpoint to use “application/x-www-form-urlencoded” as its Content-Type and sent the data as string not JSON object.
The best way to store this token is to use AngularJS module named “angular-local-storage” which gives access to the browsers local storage with cookie fallback if you are using old browser, so I will depend on this module to store the token and the logged in username in key named “authorizationData”. We will use this key in different places in our app to read the token value from it.
As well we’ll add object named “authentication” which will store two values (isAuth, and username). This object will be used to change the layout for our index page.
Step 6: Add the Signup Controller and its View
The view for the signup is simple so open file named “signup.html” and add it under folders “views” open the file and paste the HTML below:
1 2 3 4 5 6 7 8 9 10 |
<form class="form-login" role="form"> <h2 class="form-login-heading">Sign up</h2> <input type="text" class="form-control" placeholder="Username" data-ng-model="registration.userName" required autofocus> <input type="password" class="form-control" placeholder="Password" data-ng-model="registration.password" required> <input type="password" class="form-control" placeholder="Confirm Password" data-ng-model="registration.confirmPassword" required> <button class="btn btn-lg btn-info btn-block" type="submit" data-ng-click="signUp()">Submit</button> <div data-ng-hide="message == ''" data-ng-class="(savedSuccessfully) ? 'alert alert-success' : 'alert alert-danger'"> {{message}} </div> </form> |
Now we need to add controller named “signupController.js” under folder “controllers”, this controller is simple and will contain the business logic needed to register new users and call the “saveRegistration” method we’ve created in “authService” service, so open the file and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
'use strict'; app.controller('signupController', ['$scope', '$location', '$timeout', 'authService', function ($scope, $location, $timeout, authService) { $scope.savedSuccessfully = false; $scope.message = ""; $scope.registration = { userName: "", password: "", confirmPassword: "" }; $scope.signUp = function () { authService.saveRegistration($scope.registration).then(function (response) { $scope.savedSuccessfully = true; $scope.message = "User has been registered successfully, you will be redicted to login page in 2 seconds."; startTimer(); }, function (response) { var errors = []; for (var key in response.data.modelState) { for (var i = 0; i < response.data.modelState[key].length; i++) { errors.push(response.data.modelState[key][i]); } } $scope.message = "Failed to register user due to:" + errors.join(' '); }); }; var startTimer = function () { var timer = $timeout(function () { $timeout.cancel(timer); $location.path('/login'); }, 2000); } }]); |
Step 6: Add the log-in Controller and its View
The view for the log-in is simple so open file named “login.html” and add it under folders “views” open the file and paste the HTML below:
1 2 3 4 5 6 7 8 9 |
<form class="form-login" role="form"> <h2 class="form-login-heading">Login</h2> <input type="text" class="form-control" placeholder="Username" data-ng-model="loginData.userName" required autofocus> <input type="password" class="form-control" placeholder="Password" data-ng-model="loginData.password" required> <button class="btn btn-lg btn-info btn-block" type="submit" data-ng-click="login()">Login</button> <div data-ng-hide="message == ''" class="alert alert-danger"> {{message}} </div> </form> |
Now we need to add controller named “loginController.js” under folder “controllers”, this controller will be responsible to redirect authenticated users only to the orders view, if you tried to request the orders view as anonymous user, you will be redirected to log-in view. We’ll see in the next steps how we’ll implement the redirection for anonymous users to the log-in view once users request a secure view.
Now open the “loginController.js” file and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
'use strict'; app.controller('loginController', ['$scope', '$location', 'authService', function ($scope, $location, authService) { $scope.loginData = { userName: "", password: "" }; $scope.message = ""; $scope.login = function () { authService.login($scope.loginData).then(function (response) { $location.path('/orders'); }, function (err) { $scope.message = err.error_description; }); }; }]); |
Step 7: Add AngularJS Orders Service (Factory)
This service will be responsible to issue HTTP GET request to the end point “http://ngauthenticationapi.azurewebsites.net/api/orders” we’ve defined in the previous post, if you recall we added “Authorize” attribute to indicate that this method is secured and should be called by authenticated users, if you try to call the end point directly you will receive HTTP status code 401 Unauthorized.
So add new file named “ordersService.js” under folder “services” and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
'use strict'; app.factory('ordersService', ['$http', function ($http) { var serviceBase = 'http://ngauthenticationapi.azurewebsites.net/'; var ordersServiceFactory = {}; var _getOrders = function () { return $http.get(serviceBase + 'api/orders').then(function (results) { return results; }); }; ordersServiceFactory.getOrders = _getOrders; return ordersServiceFactory; }]); |
By looking at the code above you’ll notice that we are not setting the “Authorization” header and passing the bearer token we stored in the local storage earlier in this service, so we’ll receive 401 response always! Also we are not checking if the response is rejected with status code 401 so we redirect the user to the log-in page.
There is nothing prevent us from reading the stored token from the local storage and checking if the response is rejected inside this service, but what if we have another services that needs to pass the bearer token along with each request? We’ll end up replicating this code for each service.
To solve this issue we need to find a centralized place so we add this code once so all other services interested in sending bearer token can benefit from it, to do so we need to use “AngualrJS Interceptor“.
Step 8: Add AngularJS Interceptor (Factory)
Interceptor is regular service (factory) which allow us to capture every XHR request and manipulate it before sending it to the back-end API or after receiving the response from the API, in our case we are interested to capture each request before sending it so we can set the bearer token, as well we are interested in checking if the response from back-end API contains errors which means we need to check the error code returned so if its 401 then we redirect the user to the log-in page.
To do so add new file named “authInterceptorService.js” under “services” folder and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
'use strict'; app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) { var authInterceptorServiceFactory = {}; var _request = function (config) { config.headers = config.headers || {}; var authData = localStorageService.get('authorizationData'); if (authData) { config.headers.Authorization = 'Bearer ' + authData.token; } return config; } var _responseError = function (rejection) { if (rejection.status === 401) { $location.path('/login'); } return $q.reject(rejection); } authInterceptorServiceFactory.request = _request; authInterceptorServiceFactory.responseError = _responseError; return authInterceptorServiceFactory; }]); |
By looking at the code above, the method “_request” will be fired before $http sends the request to the back-end API, so this is the right place to read the token from local storage and set it into “Authorization” header with each request. Note that I’m checking if the local storage object is nothing so in this case this means the user is anonymous and there is no need to set the token with each XHR request.
Now the method “_responseError” will be hit after the we receive a response from the Back-end API and only if there is failure status returned. So we need to check the status code, in case it was 401 we’ll redirect the user to the log-in page where he’ll be able to authenticate again.
Now we need to push this interceptor to the interceptors array, so open file “app.js” and add the below code snippet:
1 2 3 |
app.config(function ($httpProvider) { $httpProvider.interceptors.push('authInterceptorService'); }); |
By doing this there is no need to setup extra code for setting up tokens or checking the status code, any AngularJS service executes XHR requests will use this interceptor. Note: this will work if you are using AngularJS service $http or $resource.
Step 9: Add the Index Controller
Now we’ll add the Index controller which will be responsible to change the layout for home page i.e (Display Welcome {Logged In Username}, Show My Orders Tab), as well we’ll add log-out functionality on it as the image below.
Taking in consideration that there is no straight way to log-out the user when we use token based approach, the work around we can do here is to remove the local storage key “authorizationData” and set some variables to their initial state.
So add a file named “indexController.js” under folder “controllers” and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 |
'use strict'; app.controller('indexController', ['$scope', '$location', 'authService', function ($scope, $location, authService) { $scope.logOut = function () { authService.logOut(); $location.path('/home'); } $scope.authentication = authService.authentication; }]); |
Step 10: Add the Home Controller and its View
This is last controller and view we’ll add to complete the app, it is simple view and empty controller which is used to display two boxes for log-in and signup as the image below:
So add new file named “homeController.js” under the “controllers” folder and paste the code below:
1 2 3 4 |
'use strict'; app.controller('homeController', ['$scope', function ($scope) { }]); |
As well add new file named “home.html” under “views” folder and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<div class="row"> <div class="col-md-2"> </div> <div class="col-md-4"> <h2>Login</h2> <p class="text-primary">If you have Username and Password, you can use the button below to access the secured content using a token.</p> <p><a class="btn btn-info" href="#/login" role="button">Login »</a></p> </div> <div class="col-md-4"> <h2>Sign Up</h2> <p class="text-primary">Use the button below to create Username and Password to access the secured content using a token.</p> <p><a class="btn btn-info" href="#/signup" role="button">Sign Up »</a></p> </div> <div class="col-md-2"> </div> </div> |
By now we should have SPA which uses the token based approach to authenticate users.
One side note before closing: The redirection for anonymous users to log-in page is done on client side code; so any malicious user can tamper with this. It is very important to secure all back-end APIs as we implemented on this tutorial and not to depend on client side code only.
That’s it for now! Hopefully this two posts will be beneficial for folks looking to use token based authentication along with ASP.NET Web API 2 and Owin middleware.
I would like to hear your feedback and comments if there is a better way to implement this especially redirection users to log-in page when the are anonymous.
You can check the demo application on (http://ngAuthenticationWeb.azurewebsites.net), play with the back-end API for learning purposes (http://ngauthenticationapi.azurewebsites.net), and check the source code on Github.
Maybe it is just me, but app took like 10sec to register at the first time…? after that it worked fine. However, this looks interesting, I haven’t used angular before but it would be cool to try out these things. I actually used token based auth earlier with my multiplayer html5 game, but it was made using against express server (nodejs) and jsonwebtoken. What do you consider as good backend for single page applications? Other than ASP?
Thanks Mauno for your feedback.
Well I usually use ASP.NET Web API as a back-end for building APIs, but you have tried on the best back-end servers nowadays, the MEAN stack (MongoDB, Express, AngularJS, and Node) is very popular now when you build SPAs as you can share some JS code between your client and server. Good luck in using AngularJS, one of the best front-end frameworks!
Okey & thanks for sharing your opinions!
Great stuff, thanks for sharing. I’m working on an Web API 2 implementation that requires Network Load Balancing, and I’m trying to figure out how to get it done properly. The main problem I have is this: We can have “N” instances of the web api (let’s say 15 for example) in the farm. So, if a client generates an authentication token and its valid for server 1, how do I make sure it also works for the other servers in the farm? Or, is there a way to move the token generator/validator to an external URL instead of ” TokenEndpointPath = new PathString(“/token”) ” so it can be accessed from all the different instance of the Web API?
Hi Jonathan, please check this url which answers your questions:
http://stackoverflow.com/questions/21102252/how-are-bearer-tokens-stored-server-side-in-web-api-2
As long you are using the same machine-key for encryption for all servers then the tokens will be validated correctly on any server on your farm.
@Mauno — I think the initial delay may have been caused by entity framework having had to initially build the database as this was indeed a ‘code first’ implementation… otherwise, it should have been very fast.
@Jonathan — I’m not sure if the token is stored in the db automatically for retrieval by multiple instances…. however if it is not, then perhaps adding in code to save the token into something like Redis or SQL Server (accessed by many instances in the farm) prior to issuing the token to the client would work.
Thanks Mark for your reply, the tokens are not stored in DB, they are self contained so it should work if you have web farm.
Thank you Mark
Great article! Very helpful for me, since I’m just getting started with ASP.NET.
Just a (probably stupid) question, since I’m not familiar with how azure websites work:
You have a decoupled WebAPI and AngularJS app implementation, which is really nice. But where did you implement the serving of the shell page (index.html)? There should be a route for that somewhere, right? I don’t know if I accidentally skipped it or what, but I didn’t find it in the tutorial.
Hello Simone,
Glad that the article was beneficial for you.
Well the “Index.html” is not a partial view, it is the only full html page in the app.
It is get served by default without typing “index.html” explicitly because in Azure website there is setting called “default documents”. Because it is named “index.html” its getting serviced directly. It will be the same case if it is named “default.aspx” or “default.htm”..etc If it was named something else like mypage.html then I should handle this explicit to service it by default.
Hope this answers your question.
It does, thank you Taiseer
I imagined it had to do with default settings of the azure service, but I wanted to be sure. Thank you for the clarification!
Is there an easy way to enable TwoFactorAuthentication(Email) on your example ? I spent the whole day trying to figure it out but it seems I just don’t get any closer. Thanks a lot!
Hi Bogdan, I’ll check how I can implement this in the coming two days, Hopefully I’ll come out with something good. Will keep you updated.
Hit F5 … so far so good…
Then registered a new username with password and confirmation keeping my fingers crossed waiting a few seconds for database data to generate…
Entered my new created username and password on login form and then…Yeeeeeeeeeeeeeeees!!!!,
Everything works like a charm… Now that’s what I’m talking about!
I must say I loved your “Nuget bottom-up” stepped approach dealing with Web API 2 OWIN. That’s pretty clean and concise.
Decoupling Web API 2 OWIN services on back-end and AngularJS app implementation on client in two separate independent apps is the way I see things. 110% with you on that approach. Good job.
Thanks mawashikid for your sweet comment
appreciate it.
Decoupling the front-end from the back-end is the right way to build SPA, really you can switch to any back-end in future.
Hi Taiseer,
Excellent article. Thank you.
The VS2013 ASP.NET MVC project template comes with an AccountController that implements the creation of Roles and UserRoles, so do you have an example of how to implement this in your spa solution?
Any help appreciated.
Regards,
Paul
dynamically generate routeProvider?
Yup it an AngularJS service comes when you inject ngRoute.
Hi Paul, you are welcome. Glad you liked the article.
Currently no, but I will consider looking into Roles and will keep you updated if I come with something useful
Hi Taiseer,
Appreciate your efforts in creating and posting this article as well as source to GitHub.
Mixing latest and the greatest of angularjs, owin, etc, job well done!
Thanks a ton!
Cheers,
–Kaush.
Hello Kaush, appreciate your nice comment..Glad that this article was helpful
Keep tuned as I want to add more features to this tutorial.
Looking forward to it!
Excellent read. Thank you for detailed explanations.
I tried commenting out the UseCors line and not sending the Access-Control-Allow-Origin header and my solution still worked. I suppose It’s because both client and server are on same host. Although ports are different. Should fail.
Thanks Alex, glad you enjoyed reading it.
If you are using IE then it will pass, try using another browser and the call will fail if the ports are different.
Hi Taiseer,
Excellent tutorial, thank you for sharing.
I was wondering if there is a way to use your solution to connect to a “custom” database. At work, we have a custom-made Users table in our SQL Server database that stores usernames, password hashes, etc. but it was created years ago without any consideration for OAuth, Identity, etc. Perhaps some way to intercept the calls to the Identity database and change them to target a differently structured database?
I’d appreciate your thoughts.
Hello djikay,
well it is pretty simple to implement this, you do not need to use ASP.NET Identity to implement generating/consuming tokens, in step 10 from the first part of this tutorial you can replace this LOC IdentityUser user = await _repo.FindUser(context.UserName, context.Password); with your custom logic to verify credentials and everything will remain the same, hope this answers your question.
Glad it was beneficial for a solution you are working on
Many thanks for the quick response, Taiseer. As I’m very new with this technology and methodology in general, I wasn’t able to actually implement what you said or even use your solution. I’ve downloaded your code from GitHub but it doesn’t run locally (I get an IIS Express error page), probably because it’s trying to connect to your database instance instead of my own, and I’ve no idea what to do to fix this. I’m really confused about all this, but it’s entirely down to my own limitations and not your code. For my particular requirements, I can’t use IIS (I’m self-hosting using OWIN) and I have installed my own copy of SQL Express 2012, so my development environment is different from your article. Anyway, once again, thank you for a wonderful article and hope one day to be able to understand and use it.
Nice post. I guess the next step is two factor authentication – http://blogs.msdn.com/b/webdev/archive/2014/02/18/adding-two-factor-authentication-to-an-application-using-asp-net-identity.aspx
Thank you for this, it really helped me understand and implement token based authentication. If you’re adding features, I vote for role based access control. I’ve been having trouble restricting access to specific pages or elements based on the user’s role. My scenario will have the user’s roles returned by the server in the form of a number 1-6, but I’d love to see you cover this topic in any form.
Thank you!
Justin
Hi Justin,
Glad it was beneficial for you, the next part will cover roles, this has been requested by many readers. I’m working on it and hopefully it will be published soon.
Hi, Taiseer! Thanks for your awesome articles! Have you published something about using roles? Thanks again for you response
Hi,
to be honest not yet, not enough time:(
Hi Taiser,
Just to let you know…. I’m also interested in how you would implement roles.
BTW: This is a great article and is very much appreciated.
Regards,
Paul
I also interest in Roles too. But I think it will take time to write it. Below is my idea for further working with Roles in Web Api:
- Work with Claims-based identity
- Write a custome Authorization Filter.
How do you think about this approach, Taiseer?
Taiseer, is there any possibility to have the client side in backbone? Thanks!
Hi Alex,
I’m really sorry but I do not have solid hands on experience with backbone, but I believe it is really easy to transfer it. Why not to fork my project and once you applied this using Backbone I’ll update my post to point to your repo.
Adding a custom IdentityUser would be a good addition to the tutorial.
Could there be a single routes file and separate the views and controllers for modules?
Hi Rodrigo,
Could you elaborate more please? You mean that application structure should be changed to match the modules?
is more a question
is not regarding their application
I want to separate module in my application
app
controllers
views
policies
services
- modules
—- main
——– controllers
——– views
——– service
——– index. html
—- financial
——– controllers
——– views
——– service
——– index. html
app.js
And only can to have a file of routes, these routes must come from the api for user permission
I like your example of authentication,I will implement something in my app
But my question is how to create dynamic routes for module after logging
you can give me a help to go study and search?
Try looking here:
http://alexfeinberg.wordpress.com/2014/04/26/delay-load-anything-angular/
Or here
http://alexfeinberg.wordpress.com/2014/04/26/delay-load-anything-angular/
Thanks Alex, Taiseer
I not use RequireJS, I use the module’s own angularjs
Reference: http://clintberry.com/2013/modular-angularjs-application-design/
Can I change $ location.path (‘/ orders’); to windows.location = “other paste”?
Because I want to use another file for routes
My idea is:
app
- controllers
- views
- js app.
- main
—- modules
—— module1
——— controllers
——— views
—— module2
——— controllers
——— views
—- app. js
ie after login I want to redirect to another module and have my app.js file with the routes according to the user’s permission, dynamically
Taiseer I m using your app as the base of my app
I’ll be glad if you help me on that last question
Hi Alex,
You can do redirection using window.location but I believe this will do full postback to the server i.e hitting refresh button (F5). I cant get what you are trying to do with app.js file and permissions
Great tutorials Taiseer. Have you tried passing the Authorization headers with jsonp requests? I couldn’t get that working, the headers are never passed.
Hi Ravi, you are welcome, to be honest no, but why it will fail? Can’t you enable CORS in the back end API and stop using JSONP?
Nice Work Taiseer!!! good introductory start for log in functionality in angular. Was very helpful. Thanks
Thanks Asad, glad it was useful
keep tuned as I’m implementing refresh tokens in detailed post.
Hi Taiseer Joudeh
Thanks for the material helped me a lot
I have a question
I see that is in the memory the user token through clains
I’m developing a multitenancy, recognize my schema for subdomains, but I can leave in memory my schema without need to always check?
Hi Maxwell,
Can you elaborate little bit more about your scenario? Can’t understand what you are trying to achieve.
Hi Taiseer, I’m having a problem with register method.
I don’t know why, the saveRegistration method in authService factory is always sending a post request with a Content-Type text/plain header and the request fails. I’ve tried to force including headers in the $http.post but angularjs always send the post as text/plain.
Any idea about what could be the problem?
Hi Jose, can you make sure it is Post request or a preflighted request (Options) request which issued by the browser to check if your origin is allowed send post request to this API? If its preflighted request make sure that your API supports CORS correctly.
Excellent series! Everything seems to work out great – with one exception. I’m seeing that when I attempt to log in, the browser is sending a “preflight” OPTIONS request before it calls the “~/token” url. This results in a 400 Bad Request, where the payload says {“error”:”unsupported_grant_type”}. How do we work around the browsers preflight request for CORS requests?
Hi Todd,
Are you sending the request against the API I’ve built or something you are building?
I’ve tried it and its work correctly, anyhow if you are using PostMan I’ve prepared the request for your here: https://www.getpostman.com/collections/1ff438e088efac658be8 you can import it and test it out.
If your building new API; double check that you’be enabled CORS correctly on the API.
Let me know if you need further help.
I guess I should have prefaced my previous comment. Everything seems to work fine except for one thing, which is probably easily fixable: If I log in successfully, then manually navigate back to “/login” (via the address bar) and enter my credentials again, the browser sends a preflight request instead. It fails with a 400 sending back “{“error”:”unsupported_grant_type”}”. The code then goes through the failed path, in which “logout” is called. Then if you go to log in again, the request goes through fine. This is because we’re not adding the “Authorization” header in that request (since we don’t have the token anymore because we just logged out). Would the solution to the problem be in the loginController when “login” is called to first check if the user is logged in. If they are, then skip calling the authService.login() function..? Or, if the user navigates to “/login” but they’re already logged in, we navigate them to somewhere else?
Hi Todd,
You are correct I was able to reproduce this issue, well I think it is better to redirect the logged in user to the /home view if the are logged in.
I will check why it is returning bad request when the user is logged in, most probably because am sitting the authorization header as you suggested.
Another alternative that I think feels better, is to just call Logout right before the Login service call is invoked. That way, there’s no need to do any special-case redirection. Thoughts?
Great article, can you recommend a resource that would cover using openId providers like Facebook, Google, etc. along with this?
Hi Larry,
Just wait couple of days and I’ll publish the 4th part which covers external logins. Subscribe to my blog to keep tuned.
Hi Larry,
I’ve blogged about this in detail, you can check it here: http://bit.ly/1rfBOW6 hope it will be useful for your case.
Hi,
I’ve been looking for a way to do authentication with web api and angualrjs for a while and i came across this article, which is not only excellent and well explained, but has help out out with a project i’m working on.
I have stumbled across an issue and unfortunately its with our friend CORS and wondered if you have seen this happen before.
First time round on authenticating it hits:
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
but that is as far as it goes before returning: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource.
on second time it hits both:
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add(“Access-Control-Allow-Origin”, new[] { “*” });
using (var authRepository = new AuthRepository())
{
IdentityUser user = await authRepository.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError(“invalid_grant”, “The user name or password is incorrect.”);
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(“sub”, context.UserName));
identity.AddClaim(new Claim(“role”, “user”));
context.Validated(identity);
}
and in it goes and everything works.
I’ve lost a couple of hours looking into what it could be and have come up against a brick wall.
Thank you
Hi Steve, thanks for your sweet comment, glad I was able to help.
Could you please check “Todd Behunin” comment thread on this post. Most probably you are trying to login again while you are already logged in. Most probably there is bug in the JS code. Can you please confirm if this is the same issue?
Bang!! exactly the same issue as “Todd Behunin” was having. I can’t believe I missed it. I used exactly what Todd suggested “just call Logout right before the Login service call is invoked” and it worked and why wouldn’t it and its so simple. Think you get to a stage when the banging of a head on the desk clouds simple thinking.
Thank you Taiseer and thank you Todd!!
Taiseer keep up the good work
Great and your are welcome
I’ll update the source code and code snippet soon to fix this.
Glad to know the work-around I proposed may have helped someone!
Hi Taiseer,
Thanks for great article.
I am stuck in one problem. I have one MVC app in which i want to use token based authentication. In this app i am using web api for all database communication. I am also generating access token on webapi with the help of your article. This part is working fine. Now in application part, when user login first time i am generating acces token and store that in cookies. My question is what should i do here for authentication. for now i am using form authentication by using that i am showing user is authenticated. can you clear me on this. how app flow should be once you get the access token and on which global event i have to work on.
Thanks
Hello Manish,
If I understood your correctly, In my case where I’m using AngularJS as front end app, I’m storing flag named “IsAuth” along with the access token I’v received on HTML5 local storage. Based on this flag I control UI elements only so I can show/hide links tabc etc…So I believe you need to do this manually too in your MVC app. One question here: Your MVC app and Web API are living on same project? If this is the case then User.IsAuthenticated property should be set and you can control UI elements. Hope this answers your question.
so how do u access the role value added to the claim from angularjs?
You can return the role value along with the “/token” response and you’ll have it in your front end app. You might need it to control UI elements only but do not depend on this value for doing any kind of authorization on the JS application; anyone can tamper with this value using developer tools.
i saw in your example you doing this but the token is encoded and so i don’t see how you can extract the role from the token?
Hi Marco,
I meant to return it in the response not in the token it self, so it will be on the same level as fields (expires_in, userName, token_type, etc..) if you couldn’t do it let me know and will prepare gist for you showing this.
Thanks for this awesome set of articles! I am yet to discover how to set the encryption keys, encrytpion algorithm and signature algorithm for bearer tokens manually. Seems like the only option is by setting the machinekey node in the web.config file. The idea is to be able to read these settings from a custom location in order to be able to deploy multiple instances of the same application. Any chance you know how to do this? I have searched everywhere and no luck. Thanks in advance for your help.
Hi Mario, glad you liked the series.
Well if you have control on your web farm; then I’ll unify the encryption/decryption keys on machine.config level not web.config.
You can try to use middle-ware “Microsoft.Owin.Security.Jwt” where I believe you can set your keys manually, not sure about this but need to read more about it.
Let me know if you find out how to do this
Thanks for the lovely tutorial, Can you get the Part 1 running again, the link is not working.
You welcome, thanks for your note, there is plugin for caching I’m using on my blog and it seems it is not working properly! Link is working again.
Hei Taiseer,
Have you any suggestions (ideas/ links) on how I can get my front web which is an MVC5 app to connect to the WebApi from part 1. To be concrete I like to use MVC5 frontweb that does same job as your angular project here. Any help is much appreciated.
First of all huge thanks for this. This was “the” article that helped me get most
Regards
Gustav
Hi Gustav, glad that it was useful,
Are u planing to use the API to support different applications or just your MVC 5 website? If just you have the MVC app then you can use the MVC5 template with individual accounts which contains all this features and much more such as 2FA and email confirmations. I find asp.net/identity is good place to check too. Hood luck in implementing the project!
Hello Taiseer
Thanks for the wonderful article, I was able to implement the same and everthing works like a charm. But currently I am bit confused and need help.
Instead of implementing the Web Api with an angularjs app I integrated the same with MVC app. Now my query is after authenticating and getting the token where should I store the token in cookie or session so that for subsequent request I can use that.
You are welcome, glad it was useful,
It is up to you where to store it, both should work, but If I were you I will store it in Cookie, as well you can store it in HTML5 local storage and access it via JS.
Hi Taiseer,
I’m new to API world… I download your application … setup it on local… and started exploring it…. everything working as expected… when i start Fiddler in parallel (so that i can record all activity) .. Login go to orders… logout… and then if i rerun (Replay> Reissue Request) orders in fiddler it gives me the orders… where as this is fired after user is logged off…
So if any browser the authentication bearer token is set… it can pull any information… correct? (even after logout)… unless the claims/or token gets expired.
Is there any way to avoid this?
Hi Mark,
Well there is no “direct” way to revoke granted access tokens or do “logoff”. if the user has the token then he can access the secured server resources until the token is expired.
So in some situations it is better to use the refresh tokens along with the access token. So you issue short lived access token (30) mins and you use the refresh token to obtain new access tokens. The nice thing here that refresh tokens can be revoked from the backend system so there is control on them,
You can read part 3 which talks about refresh tokens, and you can see how I manage to do the logoff in AngularJS application on part 2.
Hope this answers your question.
Hi Taiseer,
I hope my comment and question does not come through twice, I did try comment from my phone, but it died, and I assumed it did not go through as I don’t see it here.
Before I ask a question, I just would like to say thanks. for this brilliant article, and by the looks of things, many people do indeed feel the same. Certainly a job well done.
I have implemented your concept into a web application that I am currently writing. While both parts in in our control, our API and UI does not reside on the same server. We have mainly done this so we could scale the various components in our system independently from each other. So your post did allow me to tie up a few loose ends, specially regarding bearer tokens.
While everything works exactly as expected, I have one issue I was hoping you could help me with. I have just started integrating SignalR into the Angular application, I am sure you know that while SignalR does support various methods of performing its server connection, I would like that the “primary” mode does work. Websockets. The issue I am having is that with websockets we cannot attach the authorization header as we do for example with our api requests. So I have attached the token in the query string object exposed by SignalR. I started out by creating a new class implementing the SignalR Authorize attribute. In the AuthorizeHubConnection method, I can successfully get the token from the incoming request.
It is now at this point where I am fairly stuck. From a bit of research I can see that I do need to set request.Enviroment["server.User"] = new ClaimsPrinciple(ticket.identity);
The ultimate action part of my question would be, once I have the token in string format, how do I now get a AuthenticationTicket from this string and then pass that to the ClaimsPrinciple constructor?
Regards
Louis
Hi Louis,
You are welcome and really glad that my posts were helpful in implementing your “exciting” solution, I know about SingalR but never done serious development with it yet.
To understand what you want to achieve here clearly; you have the encrypted/signed token on the back-end (AuthorizeHubConnection) method and you want to find a way to de-serialize it in order to build Authentication ticket and get identity/claims from this token string, is this right? If this is the case; currently I’ve no clear answer and I need to investigate it more. As I told you I’m not familiar with SignalR but I’ll try to find out something helpful.
If you were able to find a solution for this please share it with us here.
Hi Taiseer,
I have just got everything working. I would love to share this with people out there. I searched the net and could not find a solution and in turn just knuckled down and did some real coding for a change and invent stuff
Could I possibly email the details through to you and you perhaps add it as a follow up on your post?
that way people looking to implement any of these items will find all the related information together in one place?
You can dm on Twitter @Louis_lewis
Regards
Louis
Sounds great, followed you on twitter, you can email the details using the contact page on my blog.
i try this everyting work but error log not showing on chrome..ie works file
for example , when i try login with wrong password ie tell me i put wrong script but chrome not…
do you have any idea?
Hi Brian,
Use the developer tools to debug the issue (F12) I can’t trouble shoot this.
sorry..i just test it again it works.
Thanks for the great article.
A basic (maybe unrelated) question: why are you using the data- directives instead of ng- (as in data-ng-view instead of ng-view).
Thanks!
this not work with ie
Hi Alex,
What is exactly not working? What is your version for IE? Should be IE9 and higher and this solution has been tested on all the major browsers including IE.
Adding my gratitude to the long list of similar comments. Excellent article and almost a perfect match for my current app project. You just saved me a couple of days researching/ developing!
Thanks!
Sounds great, really glad that this series of posts helped you
Let me know if you need further help.
Hi
Great article and really helpful. I’m running my project with this setup and it works really fine. However if I only have the “Authorize” attribute on the api, I’m able to get access to my views and only the api would be able to reject me. How would you make use of the auth server on the router lever? Basically it could be wether a view/route should be available public or private (authenticated users)
Best regards
Rasmus
Hi Rasmus, glad to hear that code is working perfectly on production environment.
Well I’ll direct you to this link where you will have clear idea on how you can listen to the event “routeChangeStart” and check if this route can be accessed anonymously or it is only available for logged in users (with token).
Let me know if this help in checking it on the Angular router service.
Hi,
Thank you for your great effort and this beautiful sample , i could use your sample and it is useful for me, but i have a problem , I’ve updated the solution with nuget package manager and the version of owin changed to 3.0.0 and the version of asp.net web api became to 2.2 , then i always get an error access_denied after logging in google when i use external login , i want to know if you have checked your sample with the latest version of packages or not , and is there any solution for me
You are welcome Masoud, always happy to help
Yes I’ve been told this but really didn’t have the time to investigate what happened to OAuth.Google library. I’ll check this during weekend and will keep you updated. If you find solution please share it with us.
Hi Taiseer,
Very good blog. It helps me so much.
I have one question: Now, I apply your method to my personal project (not follow exactly your instructions).
I applied:
- ShellPage
- authService / authInterceptorService
- home / index / login controller
And each time I press reload the page, the Top Menu do not display:
- Welcome …
- Logout
Do you have any idea?
Hi Trung, glad it was useful
Well I believe that you forgot to call app.run method in your app.js file which is responsible to fill this data once the shell page is loaded, check the highlighted code here. If this didn’t work make sure you are using the “indexController” correctly on the shell page which is responsible for these feature.
Cool. It works!
Thank you so much (y)
Could you please explain me in step 9 as the updated menu works after User login I’m not getting the update menu navigation that is required to change the css variable modicar for this condition and refresh the screen.
<li data-ng-hide = "! authentication.isAuth"
Hello Ederson,
Sure no problem, the navigation bar in the index.html page uses a controller named “indexController”, if you check the LOC https://github.com/tjoudeh/AngularJSAuthentication/blob/master/AngularJSAuthentication.Web/app/controllers/indexController.js#L9 you will notice that I’m passing the “authentication” object and checking if the user is authenticated or not, based on this I’m hiding/showing this list item. Hope this clarify your concern.
Hello Taiseer,
It turns out that even with this object authentication returning true with the logged User is not updating my navigation menu not know if you need to add som css for checked as another example to run the hide-ng. Add.ng-ng-hide-hide-add-active also noticed that you do not add angular.module (‘app’, ['ngAnimate']); veriquei as another example has some part of css or javascript more work is needed to update the navigation bar?
You do not need ngAnimate in my case, this module is used once you navigate between views to add transition effects. As well there is no need for any extra css classes, “ng-hide” is built-in AngularJS directive. Make sure you are setting the controller to the navigation section in HTML as this LOC: https://github.com/tjoudeh/AngularJSAuthentication/blob/master/AngularJSAuthentication.Web/index.html#L16
Yes I am configuring the controller for the navigation section in the HTML but still not updated if I press F5 appears menus mean that my variable is populated in the controller.
There’s something that is taken when clicked on login that makes updating the navigation menu?
Even indexController being outside the ng-view it updates itself with the changes in the scope of the controller?
This example will work if I upgrade the version of Angular v1.2.18?
I have same problem.. User menu is not updated..