Code

Deal with users authentication in an AngularJS web app

, 36 Comments

This post is meant to share some thought about the main issues related to the user’s authentication in an AngularJS web app.
This post is about how to maintain, and recognize, the status of authentication of an user (that is, if he’s logged in, or not) between the different routes of a web application. Moreover it also explains how to handle the case of a not authenticated user, who is trying to access to a page, that requires the user to be logged in.

Before going into the details of my approach, it is very important to clarify that, because the user has full controll of the browser, each control implemented with front end technologies, must (!) be repeated also in the backend.

Recognize an authenticated user

There are probably several ways to recognize an authenticated user; infact it’s possible to set a global variable, or create a cookie… but my favourite way to reach the objective is to use an AngularJS service.
This approach give me several advantages.
The first advantage is strictly related to the real nature of each AngularJS service; services are singletons, so there is only one instance of each service… and this allow to share data between different views, controller, directives, filters, and others services, without the need to overpopulate the global scope.
The second thing that I like about using a service, is that it’s possible to use it also to store others informations about the user (when he is logged in).
Just as example this is a simplified version of the service that I used to recognize if the user is already authenticated.


services.factory('UserService', [function() {
	var sdo = {
		isLogged: false,
		username: ''
	};
	return sdo;
}]);

Are you wondering about how to use this service?
Well the first step is without any doubt to inject it, everywhere it is needed; for example in the controller of the login route:


var controllers = angular.module('myApp.controllers', []);

/* ... */

controllers.controller('loginController', ['$scope', '$http', 'UserService', function(scope, $http, User) {

}]);

In the loginController controller, as you can see, it’s defined the login function.
Here for the sake of simplicity I omitted a lot of superfluous code… infact the most important thing to note is how I used the success, and error callback of the xhr request to set the properties of the UserService service.


/* ... */

scope.login = function() {
	var config = { /* ... */ } // configuration object

	$http(config)
	.success(function(data, status, headers, config) {
		if (data.status) {
			// succefull login
			User.isLogged = true;
			User.username = data.username;
		}
		else {
			User.isLogged = false;
			User.username = '';
		}
	})
	.error(function(data, status, headers, config) {
		User.isLogged = false;
		User.username = '';
	});
}

/* ... */

In this way, everywhere I inject the UserService service, UserService.isLogged tells me if the user is already authenticated or not.

Mantain reserved content… reserved

Every not trivial website out there has different type of pages; there are pages that everybody, even a casual visitor should be able to see, and others pages, which require the user to be logged, or to have particular administrative grant.
For a web application, and in particular for a web app powered by AngularJS, the concept applies equally, just replace the word ‘page’, with the word ‘route’.
So now, it’s necessary a simple way to say who can see each route of our web app… at the end I come out with the following solution.
The $routeProvider is used to configure routes, and this is really simple with AngularJS:


app.config(['$routeProvider', function($routeProvider) {
	$routeProvider.when('/login', { templateUrl: 'partials/login.html', controller: 'loginCtrl' });
	$routeProvider.when('/main', { templateUrl: 'partials/main.html', controller: 'mainCtrl' });
	$routeProvider.otherwise({ redirectTo: '/main' });
}]);

The previous snippet uses the AngularJS when() function.
It accepts two parameters: the first parameter is the path of the route; the second is an object containing the information to be assigned to the current route.
So, this object can be used to store the information about the level of accessibility of each route. As example, the following snippet shows how the definition of the login route looks like:


$routeProvider.when('/login', { 
	templateUrl: 'partials/login.html',
	controller: 'loginCtrl',
	access: {
		isFree: true
	}
});

Now, everytime the user try to navigate to a new route, we have to check if he really can access that specific route.
I wrote a directive for this purpose:


directives.directive('checkUser', ['$rootScope', '$location', 'userSrv', function ($root, $location, userSrv) {
	return {
		link: function (scope, elem, attrs, ctrl) {
			$root.$on('$routeChangeStart', function(event, currRoute, prevRoute){
				if (!prevRoute.access.isFree && !userSrv.isLogged) {
					// reload the login route
				}
				/*
				* IMPORTANT:
				* It's not difficult to fool the previous control,
				* so it's really IMPORTANT to repeat the control also in the backend,
				* before sending back from the server reserved information.
				*/
			});
		}
	}
}]);

I omitted some details from the previous snippet; however the thing that is most worth to be noted is how I used the directive to register a callback in the $rootScope, for the $routeChangeStart event, fired before that the route changes.

Bruno

I'm Bruno Scopelliti, web developer from Bologna, Italy. I use this space to share my experiences, experiments and thoughts. I'm also on Google+, Twitter, and LinkedIn.

More Posts

Standard
  • Pierre Tardy

    very interresting!

    It’s a bit weird that you have to create a directive to watch for route change. where I’m supposed to put this in the dom? in ? in the same as ng-app?

    Will this work to deny access on the first route resolution?
    i.e. what if if user goes directly to mysite.com/#/restricted

    btw, there is a small typo in the code. UserSvr is actually bound to ‘Auth’ in the DI, and used as UserSvr

    • http://brunoscopelliti.com/ Bruno Scopelliti

      Hi Pierre,
      Thanks for your comment, really constructive!

      1) I used a directive for the sake of reusability… in this way it’s really easy for me to adopt this approach in different project…
      You can insert the directive everywhere you want…
      Usually I use

      If you don’t like to use a directive you could use the angularjs run api:

      angular.module(‘yourApp’, [/* all your stuff*/]).run(['$rootScope', 'authSrv', 'userSrv', function(root, auth, User) {
      root.$on('$routeChangeStart', function(scope, currView, prevView) { });
      }]);

      2) You are right… when you load the first route prevView === undefined… so this won’t work on the first route resolution… but this is really easy to fix:
      It’s enough to bind the callback to the event $routeChangeSuccess (instead of $routeChangeStart), and use currView (instead of prevView).

      angular.module(‘yourApp’, [/* all your stuff*/]).run(['$rootScope', 'authSrv', 'userSrv', function(root, auth, User) {
      root.$on('$routeChangeSuccess', function(scope, currView, prevView) {
      /* use currView.access.isFree */
      });
      }]);

      3) I fixed the typo.

      Thanks

      • DChang

        I agree with Pierre – seems like that routeChangeStart listener should not be in a directive, but the app itself. Wouldn’t it only need to be there, once, since that app/listener will apply to all controllers? I don’t we really need it to be reusable.

        Also, if it is in a directive, and you set the listener on the link, does there need to be something to destroy the listener when the directive is destroyed?

        I enjoyed the post, by the way. Good read.

        • http://brunoscopelliti.com/ Bruno Scopelliti

          Hi DChang,
          Thanks for joining on the discussion!

          Your point about the distruction of the directive is probably good… I need to verify; however in this case I don’t see any reason to want to destroy this directive.

          I suppose that using a directive in this case is just a matter of taste; there are not real difference in terms of performance…
          When i said I used the directive for the sake of reusability, I meant between different projects; I agree that in this case, the event attachment need to be executed only one time. When I’ve code that is not specific to a project, I always prefer to build a specific component that wraps the code… This simplifies my life when it’s the time to use that functionality in another project.
          Finally, using a directive give me some advantage also when I’ve to write the tests for the application.

  • kojilab

    Nice example and excellent explanations. I am learning AngularJS too and need to work with FB authentication and check the token, and make sure it’s not expired. It’s been a bit of a challenge for me. Using your code, how would you tie that? isLogged would not be static and would do a FB API call. So that requires to use a promise and defer stuff. That’s where I’m having trouble. The controller can only execute once the service is done executing.

    • http://brunoscopelliti.com/ Bruno Scopelliti

      Hi Kojilab.
      Glad that you found the post interesting.

      Your approach seems correct to me… But sadly I never had occasion to work with the facebook login api; so I cannot help you really much…
      However if your troubles are caused by dealing with angularjs promise, maybe this article (http://blog.brunoscopelliti.com/angularjs-promise-or-dealing-with-asynchronous-requests-in-angularjs) from my blog can help you.

      Probably in the next week I will be in the same situation in which you are now, because I’m gonna to start a personal project that will make heavily use of facebook/twitter api.

      • kojilab

        Thanks for the feedback. I’ll look at your post. Let me know how things go then. I too am working on a personal project involving FB. Maybe we can share some insights, even though your Angular knowledge seems a lot stronger than mine. What I’ve noticed, and I could do something wrong, is that when my FB calls is with a promise, I have to use $timeout. Otherwise nothing happens.

        • http://brunoscopelliti.com/ Bruno Scopelliti

          Hi kojilab,

          Probably at the end I understand what your problem was… because the answers from Facebook are asynchronous, you have to use the $apply method, to be able to use that information in AngularJS…
          However, today I published another post, more focused on the topic of the authentication with Facebook in an AngularJS application: http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app – hope this can help you.

          • guest

            Where is the code of this ?

  • http://www.facebook.com/creolo.tudinga Creolo Tudinga

    Hi! interesting article!
    I’m new to angularjs and I’m quite confused about this:
    let’s say I want to visit site.com/#/page
    when the app is loaded, the login state should be initialized (there could be a previous session).
    How can I do that?
    I can do that by going temporary to the login page every time and make a login request to the server (which should be your case), but then the app should redirect to the correct page after login.. so I need to store in a variable the correct route and redirect to it after login.
    Is this approach correct?

    • http://brunoscopelliti.com/ Bruno Scopelliti

      Hi Creolo,

      A common pattern that I saw on different websites for this requirement, is to append the requested page in querystring.

      So if you want to access site.com/#/page, but you are not logged, you will be redirected to site.come/#/login?redirectToPath=%2Fpage (use encodeUriComponent)…
      Then, if the login was succefull, and you find in $location.search() the redirectToPath key, you can use its value to load the specific route, that the user had requested before he was logged.

  • dezet

    Hello,
    Its great article, really helped me with implemeting client-side authorization with angular as interceptors feel magic for me! It might be stupid question for SPA but is there any way to prevent being logged out when reloading page? I used this article to do client-side auth with angular, got my back-side auth aswell done with node.js. Evrything works fine untill i refresh the page. I guess its normall but wanted to get sure :)

    Cheers and keep up the good work! Really appreciate it!

    • http://brunoscopelliti.com/ Bruno Scopelliti

      Hi dezet…
      glad for your appreciation, thanks…

      Coming to your question: yes, it’s normal that after the page refresh, the app “forgets” the user authentication status;
      However if you created a session on the server this will be still valid… So you can write a method, to check, after the page load, if there is any session active on the server.
      I hope this can help you.
      Regards.

      • Alejandro Figueroa

        I would like to know more about this, how would it be a good way to check after page load if there’s a session active on the server?

      • Andrew Kanieski

        I too am stumped on this. How do I prevent route change to a restricted route only once we confirm there is no session from the server? The action of checking with the server to se if there is an open session already is async, but routeChangeStart doesn’t seem to be async friendly.

      • joselo

        Thanks for your post was really helpful, I’m using AngujarJs+RailsApi and I have the same problem refreshing the page, so I decided to create a Resolver that check if is there any user session.

        https://gist.github.com/joselo/8265250

      • William Verdolini

        I use the same approach described by Bruno in other article (with auth token from server and stored in localstorage). To overcome the reload problem, during the initialization of Angular Authentication Service, I check for the token and if it exists, I load user information from server.

        https://github.com/williamverdolini/discitur-web/blob/sprint3/app/modules/user/UserService.js#L188

        Doing this I can preserve authentication also for days (depending on token expiration policies)

  • Guest

    Rather than putting the route-listening logic in a directive, simply put it in an Auth service, attach a controller to your root app element (the element with ng-app on it), such as “AppCtrl”, and inject the service there, effectively communicating to angular “My app has a root level controller which uses an authentication service that listens for changes to the route”.

  • Lars Jeppesen

    Great stuff, thank you

  • col3v

    I’ve created an angular app example, based on REST API (no cookies involved!) , it’s more elaborated than this, but works robustly.
    https://github.com/mrgamer/angular-login-example

  • geekbuntu

    any chance you could show this practically implemented with your library project?

    http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app

    tyvm :)

  • Shwetz

    It is awesome but, I am new to Angular Js can you provide working demo on JSfiddle or Plunker. That would be of great help to understand step by step process. Thank you so much in advance :)

  • Amar

    Hi, Can I get a full working code? I am new to AngularJS so. Could you please help me?

  • Tan Nguyen

    Hi! Is this safe ? cant i just modify the user bool and fake that im logged in ?

    • http://brunoscopelliti.com/ Bruno Scopelliti

      Hi Tan, have you read the post? I mean the part where I wrote:

      Before going into the details of my approach, it is very important to clarify that, because the user has full controll of the browser, each control implemented with front end technologies, must (!) be repeated also in the backend.

      • Tan Nguyen

        Sorry missed that!

  • brettalton

    Can you better explain this line? By itself, it doesn’t make any sense how you set it up.

    controllers.controller(‘loginController’, ['$scope', '$http', 'UserService', function(scope, $http, User) {}]);

    and how is it used to relate,

    services.factory(‘UserService’

    with,

    scope.login = function() {

    ???

  • rmaceissoft

    Bruno Scopelliti, first of all, thanks for the article. It helped me to understand the alternatives to communicate the login status to client side app on initial page load. However I would like to point out something.

    In a paragraph you say:

    “…There are probably several ways to recognize an authenticated user; infact it’s possible to set a global variable, or create a cookie… but my favourite way to reach the objective is to use an AngularJS service…”

    and I don’t agree with that, because I can decide go for the cookie alternative, *even* using an AngularJS service. In this example (https://gist.github.com/rmaceissoft/8109206) I got to define a method “isLoggedIn” to detect if the user is authenticated or not through the cookies, thanks to the $cookieStore provider, defined into ngCookies angular module.

    Also, I think login and logout actions could be part of this service, who knows if you could have multiple controllers requiring to invoke the same login/logout action.

    • William Verdolini

      Hi Bruno and rmaceissoft, I faced the same issue, that’s “…have multiple controllers requiring to invoke the same login/logout action…”.

      I tried to follow the “angular way” of using services to save authentication user data, as described by Bruno in this article, but I don’t know if this is the best approach, ’cause every time I’ve to check if the user is logged in, I have to use a watcher to service property…see the code:

      https://github.com/williamverdolini/discitur-web/blob/sprint3/app/modules/user/UserNavBarCtrl.js#L58

      It works, but it not seems very elegant to me…
      maybe it’s a good place to use a small directive with the watcher inside, but I’ve not tried yet
      so, maybe is it better to use cookies for this?
      other solutions?

  • http://sibo.me massimo

    great article and thank you!

  • pravy89

    I’m trying to maintain session after page refresh in
    angularjs using java. I have seen many examples but, i didn’t find
    proper solution which exactly I’m looking for.
    Please find below link for snippet code, when i click on login button it is
    calling loginUser()function in LoginController . After login,
    When i do page refresh it is going to LoginController but it is not
    going inside loginUser()function.

    According to my knowledge until we call function, it doesn’t goes inside of it.
    When I do refresh how can i call back loginUser() function.

    please help me out from these. Appreciated..Many thanks.

    stackoverflow.com/questions/23132381/when-page-refresh-how-to-call-back-function-in-angularjs

  • Bikram Basnet

    i think is doesnt allow session authentication…if user logged in user simply close the browser and again if he tries to visit the site ,it gonna redirect the user to login page whereas the session cookie is still there..

  • http://guilhebl.github.io Guilherme Becker Lamounier

    So you’re relying on a JS var to say if a particular user is logged in or not, what if some user manually changes the JS var to something like User.isLogged = true ?
    How to prevent manually hacking the JS objects ??

  • Muhammad Ali

    How can I use that directive?