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.
- Decouple OWIN Authorization Server from Resource Server – Part 5.
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="https://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 | varapp=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){ varserviceBase='http://ngauthenticationapi.azurewebsites.net/'; varauthServiceFactory={}; var_authentication={ isAuth:false, userName:"" }; var_saveRegistration=function(registration){ _logOut(); return$http.post(serviceBase+'api/account/register',registration).then(function(response){ returnresponse; }); }; var_login=function(loginData){ vardata="grant_type=password&username="+loginData.userName+"&password="+loginData.password; vardeferred=$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); }); returndeferred.promise; }; var_logOut=function(){ localStorageService.remove('authorizationData'); _authentication.isAuth=false; _authentication.userName=""; }; var_fillAuthData=function(){ varauthData=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; returnauthServiceFactory; }]); |
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"requiredautofocus> <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){ varerrors=[]; for(varkey inresponse.data.modelState){ for(vari=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(' '); }); }; varstartTimer=function(){ vartimer=$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"requiredautofocus> <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){ varserviceBase='http://ngauthenticationapi.azurewebsites.net/'; varordersServiceFactory={}; var_getOrders=function(){ return$http.get(serviceBase+'api/orders').then(function(results){ returnresults; }); }; ordersServiceFactory.getOrders=_getOrders; returnordersServiceFactory; }]); |
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){ varauthInterceptorServiceFactory={}; var_request=function(config){ config.headers=config.headers||{}; varauthData=localStorageService.get('authorizationData'); if(authData){ config.headers.Authorization='Bearer '+authData.token; } returnconfig; } var_responseError=function(rejection){ if(rejection.status===401){ $location.path('/login'); } return$q.reject(rejection); } authInterceptorServiceFactory.request=_request; authInterceptorServiceFactory.responseError=_responseError; returnauthInterceptorServiceFactory; }]); |
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.
Sir ,
Don’t we need to send in the client id and client secret too if I am implementing an oAuth mechanism ?
I have a curl call which gives me an access and refresh token. It goes like this
curl X POST vu clientID: clientSecret http://sampleurl.com/oauth/token H “Accept: application/json” d “password=pwd&username=usr&grant_type=password&scope=read%20write&client_secret= clientSecret &client_id=clientID”
Do we just add it to the data variable along with username and password ?
Getting Circular Dependency Error at
app.config(function ($httpProvider) {
$httpProvider.interceptors.push(‘authInterceptorService’);
});
I had this problem too. I’m using angular-ui-router, so I’m passing in $state into the authInterceptorService instead of $location. The problem is that angular-ui-router passes in $http as a dependancy, this creates a circular reference within $httpProvider. The solution is simple. Remove $state from your dependency injections, and use $injector instead, like this;
app.factory(‘authInterceptorService’, [‘$q’, ‘$injector’, ‘localStorageService’, function ($q, $injector, localStorageService) {
then, replace the folowing code;
$location.path(‘/login’);
with this;
var $state = $injector.get(‘$state’);
$state.transitionTo(‘login’);
Hi,
I am Testing AngularJSAuthentication which contain all 3 projects,But not able to test locally.What I did to test locally
1. In All file location Changed serviceBase as my localhos “http://localhost:32150/”
2. I hardcoded the username as
var data = “grant_type=password&username=” + “Test123” + “&password=” + “Test123”;
this data is passing when login
Facing Issue:In AuthService
At Block
$http.post(serviceBase + ‘token’, data, { headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’ } }).success(function (response) {
if (loginData.useRefreshTokens) {
localStorageService.set(‘authorizationData’, { token: response.access_token, userName: loginData.userName, refreshToken: response.refresh_token, useRefreshTokens: true });
}
else {
localStorageService.set(‘authorizationData’, { token: response.access_token, userName: loginData.userName, refreshToken: “”, useRefreshTokens: false });
}
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
_authentication.useRefreshTokens = loginData.useRefreshTokens;
deferred.resolve(response);
}).error(function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;ess(function (response) {
It is not going inside the block.
I debugged in FF:
Data is coming as: “grant_type=password&username=ajaysh&password=ajaysh”
What does mean to Post this data?
After login nothing is coming page page is same as login page.
Please help me out.
Taiseer Hi, I have noticed that when the token expires an error when entering the login.
I have solved so:
var _responseError = function (rejection) {
if (rejection.status === 401) {
$ location.path (‘/ income “);
localStorageService.remove (‘authorizationData’);
}
return $ q.reject (rejection);
};
so it works without problem, you think it’s okay solved or see a better solution ??
Hi Cristian,
What you implemented makes sense, you are removing the stored authentication data when the token is expired, so I guess it fine.
Thanks for sharing this.
Thank you for sharing your knowledge, it would be good will update what you indicated in your tutorial for new people to read it and not them this error occurs when the token expires.
I am very grateful with you and you taught me with your tutorial to handle tokens.
Greetings from Colombia.
Hi, there I just want you to take note that the .success() and .error() methods for the $http services has been depreciated by AngularJS. The site now suggest that users use the .then() method specified in their main webiste https://docs.angularjs.org/api/ng/service/$http
Thanks Gret for your comment, I guess the article needs to be upgraded to newer version of AngularJs
open-source software seems to be “deprecated happy”. Now you need to re-write you code to use .then until next year when they deprecate that and you need to re-write it again with .superThen.
Cannot find ordersController.js in same of this post.
Check in GitHub or The third part of this manual.
When I return 401 to the client Chrome opens Credentials Dialog right away before I get into Interceptor’s response handler. How does it work with a custom Login form? Can I suppress Browser login prompt?
Hi Mark,
Can you inspect the response header “WWW-Authenticate” value when you receive 401? If it was set to “Basic” then your Api is configured incorrectly, that’s why you are receiving the default browser login which asks for your user name and password, read the post again and ensure you are using bearer tokens when you configured the Api.
I see. Would you recommend using Browser default dialog or work on a custom one?
Hello,
First, I wanted to thank you for your post.
I have a question about the data in the local storage: is it normal that you don’t test if your token is expired or not when you run the client side application ?
If the user comes back after a moment, he’ll see the welcome message in the navbar and if he fill a form and submit to an Authorize action, he’ll loose everything and be redirected to the login page….
Thx for your help !
Hi Benjamin, The implementation in the post is minimal, but yes you should check for the token expiry maybe once you request a view and if its expired you redirect user to login screen before doing any request to the server.
Is it possible to implement a standard Browser Login form for a token based authentication security?
Hi, I think i’ve applied everything correctly but i don’t think my interceptor is working correctly. I wanted to be able to redirect to the login page if I try to access any other page without being authenticated, how can I achieve this?
Hi Taiseer,
Good Post!
Few questions around Security.
1. Is it really a good practice to store the token in localStorage? I feel like it wouldn’t be difficult for malware to steal this information and use the token (if not expired) to access data by calling web services and passing in this token (so long as the token is not expired. Perhaps, sessionStorage might be better given that it is accssible only within that browser/window/tab session.
2. From the first post, when authenticating user with SimpleAuthorizationServerProvider, what about signin status for the user? For example, if the user is locked out or has not yet confirmed email ,etc. will the token be issued successfully? Perhaps, you had left that out for brevity but wanted to better understand your thoughts.
Thanks,
Om
Taiseer, Good article, but this is a valid question and has not been answered by you. This method of storing tokens in localstorage is open to XSS and goes against OWASP recommendations.
Hi Taiseer
Having a problem logging in, I keep getting the following error when I log in from the ASP Web Client.
“Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:55710/token. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘*, *’).”
However, when I use ‘Fiddler’, all works perfectly fine ???
http://localhost:55710/token needs to be http://localhost:55710/oauth/token.. I had this issue for days following this tutorial.
Hey!
Any suggestions on how to validate the user to generate the authToken using a dynamic connection string?
I’ve a general database where companies that are registered are associated with the name and password of their own specific database that have the users for that company. So I need to get this data using the company name to create a connection string that’ll be used to check if the user login is valid. Not sure what direction to go on doing this.
Hi,
When i try to login, i get the below error.
{“$id”:”1″,”Message”:”No HTTP resource was found that matches the request URI ‘http://localhost:59000/api2/api/token’.”,”MessageDetail”:”No type was found that matches the controller named ‘token’.”}
Could you please advise ?
Thank you,
Erkan
change ‘localhost:59000/api2/api/token’ to ‘localhost:59000/api2/api/oauth/token’ find that part of your code and update it
Hey,
When i try to login, i get the below network error. Could you please advise ?
{
message: “No HTTP resource was found that matches the request URI ‘http://localhost:59000/api2/api/token’.”
messageDetail: “No type was found that matches the controller named ‘token’.”
}
Thanks
Please make sure that you setting the TokenEndPointPath correctly on step 9 from this post
Can you add email confirmation into this project?
Please check this post where this feature is already implemented.
Hello Taiseer
Thanks a lot for writing this crisp and clear webAPI and webclient applications.
I need your suggestion on how to design\ proceed for my scenario:
I have 2 applications(one webclient and one mobile client) which I need to authorize using OAuth Token Authentication. After authorization, these application needs to connect to other two APIs[One signalR service and one webAPI].
1. Can I use similar Auth Service for authentication \ Authorization for my two applications[WebClient and Mobile Client]?
2. How token will be validated when these applications are making connection to two APIs[One signalR service and one webAPI].
3. Do we need to validate token for every request\ operation from client applications? If yes, how?
4. What if this token is sniffed on network, and used to access WebAPI?….how to validate token at other APIs [One signalR service and one webAPI].
Regards
Robin
Hi Taiseer,
This was a very good article with excellent information on how to generate a token for use by Angular JS … Thanks!
On my local API server, I was able to register a user and I am able to get a token back, but when I try to get the Orders list, I get an error: No HTTP resource was found that matches the request URI ‘https://localhost:44305/api/Orders’.
I am using Postman and I registered the same user on your Azure API site, I then got a token and was able to pull back the Orders list from your site … So, I don’t know why when using my local API server with the same Postman syntax I am getting this error.
Any suggestions?
Dean
Hi Dean,
I guess you didn’t attribute you GetOrders method with Route(“”) or there is something wrong with your routing configurations, if you share your controller code I would be able to help.
I support an application written in VB.NET, so I setup my AngularJSAuthentication project using VB.NET … So, I understand if you are not familiar with VB.NET … By the way, I have written other VB.NET WebApi applications that work fine for standard MVC applications, but I am very impressed by how you have put together this tutorial series for implementing OAUTH Token support that can be used for AngularJS … Here is my Orders Controller:
Imports System.Net
Imports System.Web.Http
Namespace AngularJSAuthentication.API.Controllers
Public Class OrdersController
Inherits ApiController
Public Function [Get]() As IHttpActionResult
Return Ok(Order.CreateOrders())
End Function
End Class
#Region “Helpers”
Public Class Order
Public Property OrderID() As Integer
Public Property CustomerName() As String
Public Property ShipperCity() As String
Public Property IsShipped() As Boolean
Public Shared Function CreateOrders() As List(Of Order)
Dim OrderList As New List(Of Order) From {
New Order With {.OrderID = 10248, .CustomerName = “Taiseer Joudeh”, .ShipperCity = “Amman”, .IsShipped = True},
New Order With {.OrderID = 10249, .CustomerName = “Ahmad Hasan”, .ShipperCity = “Dubai”, .IsShipped = False},
New Order With {.OrderID = 10250, .CustomerName = “Tamer Yaser”, .ShipperCity = “Jeddah”, .IsShipped = False},
New Order With {.OrderID = 10251, .CustomerName = “Lina Majed”, .ShipperCity = “Abu Dhabi”, .IsShipped = False},
New Order With {.OrderID = 10252, .CustomerName = “Yasmeen Rami”, .ShipperCity = “Kuwait”, .IsShipped = True}
}
Return OrderList
End Function
End Class
#End Region
End Namespace
Here is my WebApiConfig code:
Imports Newtonsoft.Json.Serialization
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Net.Http.Formatting
Imports System.Web.Http
Namespace AngularJSAuthentication.API
Public Module WebApiConfig
Public Sub Register(ByVal config As HttpConfiguration)
‘ Web API routes
config.MapHttpAttributeRoutes()
config.Routes.MapHttpRoute(
name:=”DefaultApi”,
routeTemplate:=”api/{controller}/{id}”,
defaults:=New With {.id = RouteParameter.Optional}
)
Dim jsonFormatter = config.Formatters.OfType(Of JsonMediaTypeFormatter)().First()
jsonFormatter.SerializerSettings.ContractResolver = New CamelCasePropertyNamesContractResolver()
End Sub
End Module
End Namespace
Thanks for any suggestions you can offer, Dean
In VB.NET, the attributes begin and end with less than and greater than symbols, so after seeing my response, I noticed they got stripped from my previous Reply.
The OrdersController Class has the attribute: RoutePrefix(“api/Orders”)
The Get Function has the attribute: Authorize, Route(“”)
Hi Dean,
I will assume you are calling WebApiConfig.Register on the Startup.Configuration method, right? As well can you try to turn off “config.Routes.MapHttpRoute” and keep “config.MapHttpAttributeRoutes()”
Hi Taiseer,
When I first created my API web, I didn’t create the separate and blank Solution from the API Project, so I had to adjust the folder layout when adding the Web Project to the Solution.
So, I went back to square one and built my VB.NET based version from scratch (although, I was able to pull in the files I created in my first pass, so it wasn’t that much work).
After getting everything setup, it is now working GREAT!!!
The lesson for other readers … If you have a problem getting your version to work … First, make sure you check that you are following Taiseer’s instructions carefully … 🙂
Thanks for your quick response to help … And keep up the great work on your series of tutorials.
You are most welcome Dean, happy that your issue is solved 🙂
I followed your tutorial but dont know why its not sending me token. I got a response from google after login and got a call for http://localhost:32150/api/account/ObtainLocalAccessToken but not have externalaccesstoken. I overloaded method to check api hit and its hit if this will be method
[AllowAnonymous]
[HttpGet]
[Route(“ObtainLocalAccessToken”)]
public async Task ObtainLocalAccessToken()
{
var verifiedAccessToken = await VerifyExternalAccessToken(“provider”, “externalAccessToken”);
return Ok();
}
Please help me where im going wrong
I really have no clue, try to set a break point in method “VerifyExternalAccessToken” and debug it to see what is missing.
Thank you so much, it is very useful for me.
you are most welcome 🙂
How might I add either individual security or role based security
Check this post please.
Hey ! thanks a lot for the help here. i have a question i want to add more fields to the usermodel i’ve tried to add attributes to the sign up js controller but nothings changes i don’t know what i am missing
Hi Miriam,
Your question is very generic, if you have specific issue please let me know to help.
What am I doing wrong. When I change the information in ordersController.cs, such as add new items, they don’t show. It still only shows what was originally there. I assume it is cached somewhere and that is what I am getting, but how do I clear it.
I am really appreciate your effort. Thank you very much for share your knowledge with us.
When you login to the page, on demo application http://ngauthenticationweb.azurewebsites.net/#/home, and then logout, and you try to reach the page http://ngauthenticationweb.azurewebsites.net/#/orders, this page is briefly appeared, and then you redirected to login page. How to fix this issue?
Hi Lucky, that is right, because the validation is done on the server side, so when you receive 401 there is a redirect to the login page.
Maybe you need to check if the token is not expired or exists before sending the HTTP request, and based on this you decide to display the view or redirect to login page before.
There are a lot of topics on the net about this, do a small research and you should find something helpful.
hey, I had the same problem and finally figure it out by adding a reference to ‘$templateCache’ and call $templateCache.removeAll();
My problem was that on “log-out”, the Auth/Token cookies would get cleared and user was no longer logged in however clicking on links triggered each template and controller to load. My controllers are set up to load info from DB at startup and since user was no longer logged in, toasts with errors were showing.. This templateCache fixed the problem and I hope it helps someone who is searching.
app.run([‘$rootScope’, ‘$location’, ‘$http’, ‘$cookies’, ‘$cookieStore’, ‘$templateCache’, function ($rootScope, $location, $http, $cookies, $cookieStore, $templateCache) {
$rootScope.logout = function () {
//do logout stuff
$templateCache.removeAll();
$location.url(‘/signin’);
}
$rootScope.$on(‘$routeChangeStart’, function (event, current, previous) {
//do refresh token thing
}
});
Cheers!
Thank Alex for sharing this. appreciate your help.
Hey this is a great article and really helped me to start off interacting with my companies’ api from AngularJS. I’m a rookie with angular JS.
I have two questions.
If I wanted to pass parameters to the api call in the ordersService such as an OData filter what would be the best way to do this? I essentially have an external api which I’m using Angular to communicate with. I’ve read that using the ngResource is a good way to do this. Is that correct?
I also have tried to implement ngResource in my service but I believe the authInterceptorService is causing errors. Does that make sense? Is there another way to use the authInterceptorService for ngResource?
Hi Eric,
Thanks for your comment. To be honest I do not have a clear answer if there is an issue when using ngResource with authInterceptorService, my recommendation is to create Stack OverFlow question or search there as I’m sure you will find answer directly. Sorry for not being able to help.
Hello!
First of all, thank you for sharing your knowledge. I find your posts very useful and professionally written.
Following your tutorial, I’ve managed to build the back-end API (lets call it App1) which accepts login requests and sends back the token. I’ve also created an AngularJS application (let’s call it App2), which attaches this token to subsequent requests. The WebAPI Controllers of App1 decorated with the [Authorize] attribute correctly allow access for authenticated users only.
Now, I would like to be able to restrict access to some resources of App2. Just for the purpose of this example, let’s say that App2 will contain some WebAPI controller, which should also be decorated with the [Authorize] attribute. How can I validate the Bearer token sent to the this controller? Is there a way of forwarding it from the WebAPI Controller of App2 to App1 for validation to create some form of local token? I’d like App1 to be used only for the purpose of authentication. Further authorization should be handled by App2.
I will appreciate all help.
Regards,
Marlon
Is it possible to implement SSO with OAuth?
Yes you can do this, my recommendation is to take a look at ThinkTecture Identity Server and not to built something from scratch.
I have it running beautifully on a development server, but can’t get authentication to run on production. Do I need a “machine key” to make it work?
I assume you simply break out the three different apps (.resource server, .Web, and .API) to different domains, and then all that needs to be done is to point to the correct URL and Port in app.js of the .Web server. Is there anything more I need to do than that?
I’m sorry I don’t see function implement http://ngauthenticationapi.azurewebsites.net/token
Cloud you tell me more this function
If someone could help me with this it would be great. I have been stuck for several months now, and can’t use this. I have asked a couple of times already, but no reply.
Again, as asked before, “I have it running beautifully on a development server, but can’t get authentication to run on production. Do I need a “machine key” to make it work?
I assume you simply break out the three different apps (.resource server, .Web, and .API) to different domains, and then all that needs to be done is to point to the correct URL and Port in app.js of the .Web server. Is there anything more I need to do than that?”
If anyone could help me with this, it would be most appreciated.
Thanks
How can we add the issuer and subject claim here so that wee can implement the authorization in the application.
I have seen in JWT this can be achieved.
Can we do it in this sample application.
Really good article. Exactly what I was looking for.
I am trying to implement this on my project that is using ASP.Net Core.
Few things are not working and I am not able to establish the right migration from this code to ASPNet Core.
Do you have by chance have an example with ASPNet Core?
Hi Kiran,
unfortunately I do not have time to check this right now, but can you take a look at this URL, might be useful. Good luck
Sir, I want to ask if we don’t want to host our application at azure and want to connect the back-end to front-end as we do in a simple Web-Api or any web application. So can you just tell the way to do it, I mean do we need to make a lot of changes to do so or it will be done simply as we do for other applications just make changes to the connection string and app.js
Thank You
Excellent.
Many thanks
Hi Taiseer,
I go through you tutorial and I use it in my project. Its a good tutorial and very useful for a technology beginner.
I just have 1 question that I found while developing my application.
Question is : If a user is logged in from ABC machine and after a moment of time same user logged in different machine that is XYZ machine. In this case how we can expire the token of ABC machine. so if user come back on ABC machine and try to perform any operation then he need to login again.
Thanks once again for this article.
I found that you articles are good and solve our lots of problem while development.
Hi Satish,
If you need to implement such scenario then you need to save some state on the server and check it when the user logs in from another machine, this will break the beauty of self-contained tokens where there is no need to check the server or query and DB to validate the tokens.
Thank you for your article it is very useful.
You are welcome Amr, happy to know it is useful.
Wow. I just finished this second part of your tutorial and I am deeply impressed. This is really helpfull. Thanks a lot!
Thanks Torsten for your message, happy tp hear it is useful.
Hi Taiseer
I have used your great tutorial to create a oaut api for my app. I recvie the token from the token endpoint but if I pass this token in the header for the next api call i irecive a 401 if I add the Authorize Attribute to my method. I used the OWIN 3.0 instead the 2.0. As you know there are some breaking changes that can cause this error? Did you have an idea why I recive a 401?
here my stackoverflow thread https://stackoverflow.com/questions/40440117/token-based-authentication-using-asp-net-web-api-2-and-owin-throws-401-unautoriz
Hello Chris, I have put a small comment on your question, please try it without Autofac, if it is good try to use DI again and troubleshoot it.
Hi Taiseer,
Thanks for this example. I am using same approach in my application and I am happy with your code explanation.
I am stuck at 2 places with my application
1. Twitter Login Integration: Are you already have any solution that use same approach with twitter integration.
2. How to stop flickering when we are loading 1 page then some content we need to hide based on server response conditions. can we stop that flicker so that application flow will more smooth and end user will not be able to see that flicker.
Thanks
Hello Satish,
Thanks for your message.
Regarding point number 1, where I wrote the post, Twitter was not using OAuth 2.0, you need check if they support OAuth 2.0 and there is an endpoint for validating the twitter token.
Regarding point 2, I urge you to search more for this on Stack overflow, I didn’t use Angular for a while so I do not have an answer for this, but I believe there should be a solution to hide the UI part until you receive a response from the server.
Hey, my authorization header is always null :S i enable cors but it did not work
Seems that the interceptor not working correctly, you need to debug it my friend 🙂
Hello Taiseer,
I don’t get how indexController is called and when. I am using an angular application that was generated with a default SPA Template on Visual studio 2013 and I can’t get that indexController to be fire.
Thank you,
Radu
Never mind I’ve saw just after I’ve comment this bit of code 🙂 data-ng-controller=”indexController”
Sorry for the spam,
Radu
No problem, happy it is all working now 🙂
This is a great way to learn token based auth. Thanks Taiseer for this tutorial. Really helpful!!
You are most welcome, happy to help 🙂
Very nice series of articles. Helped me get going with the Web API and token.
Any possibility of an Angular 2 version of the SPA?
Cheers
Michael
Hello Michael,
Thanks for your comment, I didn;t play a lot with Angular 2 yet, so it is hard to compile a post about it, but I’m pretty sure there are a lot of resources out there already 🙂
Please If You can tell me how we can delete access tokens after login
Hi Taiseer,
Many thanks for all the effort regarding the tutorials. So I configured the CORS as mentioned in the article, but I keep receiving the following:
XMLHttpRequest cannot load http://localhost/Foo.API/token. The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. Origin ‘http://localhost:5000’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
I’ve struggled with CORS in the past and normally I figure it out, but this time I’m stumped.
Thanks!
How we can make the CRUD in SQL server via ADO.NET Entity Data Model
Hi Taiseer,
Is there a way to get UserID from RequestContext while using token ?
Thanks,
Orhan
I have added into claims while authorizing and get from controller but dont know if it’s best practice.
//server provider added
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, _userid));
//controller class added
ClaimsPrincipal principal = Request.GetRequestContext().Principal as ClaimsPrincipal;
string userid = principal.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier ).Single().Value;
Hi Ozgur,
This should be helpful http://stackoverflow.com/questions/26046441/current-user-in-owin-authentication and I believe you already solved it 🙂
what about if I want when another user like (admin) disable user(student) then immediately student be log out and then login again
I have Angularjs app connects to a server using API, and i’m using token authentication, when i use Postman to get the token, it works perfect, but when i’m use Angulajs with the same header and parameters i got error:400.
When i checked both requests using Fiddler, i found that the request from Angularjs is missing “Access-Control-Allow-Origin: *” header.
How to fix this?
I have the problem both apps (API and Website) are running under diffrent domains (localhost and then some port). My browser won’t allow me to access the API. Is there a way to run both applications under the same Port in Visual Studio?
Hi Taiser,
Great articles! But could you give a example for the ordersController with with connection to database?
Would you not make factory classes for all controllers?
Hi,
I keep getting unsupported_grant_type error.. The API works fine when using ARC or Postman, but fails when calling through Angular.
Hi Sameer,
I think your issue is related to CORS, it is failing when calling it using JS, right?
First, would like to thank you for such a informative post.
Noticed that the communication with back-end service is in clear text which means all secret information is available to man in the middle. Implementing SSL should take care of it.
I have closed the tab after login your site and i could able to launch the order page directly by URL.
How to redirect the user to login page when user try to open directly ?.
Hi,
I just had an weird bug and I think I should share my experience with others. So I’m using the similar setup as this one here, AngularJS + Token based authentication with ASP.NET Web API. Everything was just fine until I accidentally typed a password containing the ampersand.
OAuth unfortunately supports only ‘Content-Type’: ‘application/x-www-form-urlencoded’ and if you have the ampersand in your password you will not be able to login because everything after the ampersand will be recognized as the new parameter.
So code from this article which is preparing request body looks like this:
var data = “grant_type=password&username=” + loginData.userName + “&password=” + loginData.password;
Let’s say your password is “H&llo2”. Now the actual body which will be sent to the server will look like this:
grant_type: password
username: your_username
password: H
llo2:
Your password will always be wrong 🙁
So in order to avoid this, before we add our password to the data string, we should first encode it using encodeURIComponent function:
var data = “grant_type=password&username=” + loginData.userName + “&password=” + encodeURIComponent(loginData.password);
Maybe it would be good to add a note or a comment near this code, so that people are aware of such potential weird bugs (especially when user already changed his password and he is just saying: “I’m not able to login”, but you can’t reproduce that ;))
Hi
Is there any way we can login as different user. I have a list of users in the system. And the admin wants to login as any user. The problem is I just have the password hash in database and I can not call the /token api. Is there any other solutions I can use to sign in.
Thanks
Thanks for sharing your thoughts on despedidas de soltero en madrid.
Regards
I implemented part one which has been a massive help, thanks!
Now I’m struggling with retrieving the token in Angular 2 with typescript! Any pointers?
Cheers
Hi,
Thanks for this great post it has helped me a lot.
Refresh token works fine for 2 to 3 days but after few days it stops working. IIS return Bad request 400. It works fine by restarting/recycling API instance on IIS.
Unable to find what is the issue with the IIS or the Owin why it works fine for few days while I have set an expiry time of 90 days for the refresh token and access token expiry time 30minutes.
And I’m not using javacript to consume the api. I’m using java.
Hi Taiseer,
Awesome tutorial, the best I have found and exactly what I was looking for – thank you.
Did I miss the part where you are calling ‘fillAuthData’? Because when I refresh the browser, my logged in state changes to isAuth: false despite still having a valid token. I have just added parens () after the function definition for fillAuthData in authService to ensure that it runs every page load, but is this the appropriate way to handle this?
Thanks again!
Hi,
Great article. Is there a simple way to apply the token authentication to a non-api call e.g. on the home page load?
Thanks
Guess I’m going to have to learn how to debug ng SPAs since I can’t seem to get this one going to save my life.
Had to fix the font-awesome CDN since the one in this post now gives 403s.
I even downloaded the code from GitHub and that version won’t work either. (and by Won’t Work, I mean I try to login or sign in and get errors.)
When trying to Sign Up, I get a red box under the submit button that just says “Failed to register user due to:” … due to what?! -sigh-
Oi, my bad. Please feel free to delete this, and my comment above.
I am getting the same issue. It happened 3 days ago and am not able to resolve.
I have 5 servers running identity server 3, mongodb AngularJSAuth store. Each one configured the same way in a dev environment. One dev server stopped working. I couldn’t get it going again so I deleted the MongoDB database and tried [Signup] to autocreate but still no go. It’s returning [response.data = “”]. So response.data.modelstate === undefined. Logging provides nothing. Logging works on all the other servers. Fiddle find no http exceptions. HTTP headers look good I am using IISExpress with HTTPS. I have binded a self-signed certificate, that I generated in Powerscript, to each of the localhost ports. Same as on other servers. There is no code difference. No config difference. I reviewed my code also with the original article and nothing has changed in my code that impacts the function of the original code. And all servers are running the same code, have the same cert binding to app IP:ports. It feels like the API server app is rejecting the token request from the web server app. Yet I don’t know why, if that is indeed the case, since I have no info to go on.
Any ideas would be greatly received – even ideas for isolation that i have not already tried.
Perfect !!!
Hi,
I am new to angular and I followed your steps till now(Part 2). I am getting error when trying to login via web page (API login is working fine). So I checked your source code and switched to commit version ‘c3ef213569f1eb8b0d21cf9029545dec0987b67e’. Found the same issue there as well.
Only change I made is changed to serviceBase url to localhost one.
var serviceBase = ‘http://localhost:26264/’;
//var serviceBase = ‘http://ngauthenticationapi.azurewebsites.net/’;
Error:
Failed to load http://localhost:26264/token: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:32150’ is therefore not allowed access. The response had HTTP status code 400.
I really like the approach Back and Front here. But I have aquestion for you.
How do you handle when the token is expired ? Do you ask the user hie credential again ? Do you use refresh token (my favorite solution) ? Do you store in any secure way the credential ?
Realy great post!!!
Hi Taiseer !
I’m very grateful for your tutorials, are really helpfull. But the main reason beacuse I’m writing is that I’d like to know is there any way to read the claims on client side which are encoded in ‘access_token’, because of I want to read extra claims added on server side
Hi Taiseer,
Thaks for your post.Its really helpful.In my case i have put Windows Authentication for Web Application in IIS and Anonyms authentication for web api.In this case when i use angular interceptor its asking user name password always.How to solve this issue?
Hi Taiseer
I like this. I have implemented this solution with my app but issue is that we have two different ng-app and two different route one is for admin side and another is for user side. but login are same for both. How can we achieve this with this situation?
Hi
How can I change the table names