Authentication

The most common form of authentication is logging in with a username (or email address) and password. This means implementing a login form where users can enter their credentials. Such a form could look like this:

<form name="loginForm" ng-controller="LoginController"

ng-submit="login(credentials)" novalidate> <label for="username">Username:</label>

<input type="text" id="username"

ng-model="credentials.username"> <label for="password">Password:</label>

<input type="password" id="password"

ng-model="credentials.password"> <button type="submit">Login</button> </form>

(Note: there’s an extended version below, this is just a simple example)

Since this is an Angular-powered form, we use the ngSubmit directive to trigger a scope function on submit. Note that we’re passing the credentials as an argument rather than relying on $scope.credentials, this makes the function easier to unit-test and avoids coupling between the function and it’s surrounding scope. The corresponding controller could look like this:

.controller('LoginController', function ($scope, $rootScope, AUTH_EVENTS, AuthService) {

$scope.credentials = {

username: '',

password: ''

};

$scope.login = function (credentials) {

AuthService.login(credentials).then(function (user) {

$rootScope.$broadcast(AUTH_EVENTS.loginSuccess);

$scope.setCurrentUser(user);

}, function () {

$rootScope.$broadcast(AUTH_EVENTS.loginFailed);

});

};

})

The first thing to notice is the absence of any real logic. This was done deliberately so to decouple the form from the actual authentication logic. It’s usually a good idea to abstract away as much logic as possible from your controllers, by putting that stuff in services. AngularJS controllers should only manage the $scope object (by watching and manipulating) and not do any heavy lifting.

Communicating session changes

Authenticating is one of those things that affect the state of the entire application. For this reason I prefer to use events (with $broadcast) to communicate changes in the user session. It’s a good practice to define all of the available event codes in a central place. I like to use constants for these things:

.constant('AUTH_EVENTS', {

loginSuccess: 'auth-login-success',

loginFailed: 'auth-login-failed',

logoutSuccess: 'auth-logout-success',

sessionTimeout: 'auth-session-timeout',

notAuthenticated: 'auth-not-authenticated',

notAuthorized: 'auth-not-authorized'

})

A nice thing about constants is that they can be injected like services, which makes them easy to mock in your unit tests. It also allows you to easily rename them (change the values) later without having to change a bunch of files. The same trick is used for user roles:

.constant('USER_ROLES', {

all: '*',

admin: 'admin',

editor: 'editor',

guest: 'guest'

})

If you ever want to give all editors the same rights as administrators, you can simply change the value of editor to ‘admin’.

The AuthService

The logic related to authentication and authorization (access control) is best grouped together in a service:

.factory('AuthService', function ($http, Session) {

var authService = {};



authService.login = function (credentials) {

return $http

.post('/login', credentials)

.then(function (res) {

Session.create(res.data.id, res.data.user.id,

res.data.user.role);

return res.data.user;

});

};



authService.isAuthenticated = function () {

return !!Session.userId;

};



authService.isAuthorized = function (authorizedRoles) {

if (!angular.isArray(authorizedRoles)) {

authorizedRoles = [authorizedRoles];

}

return (authService.isAuthenticated() &&

authorizedRoles.indexOf(Session.userRole) !== -1);

};



return authService;

})

To further separate concerns regarding authentication, I like to use another service (a singleton object, using the service style) to keep the user’s session information. The specifics of this object depends on your back-end implementation, but I’ve included a generic example below.

.service('Session', function () {

this.create = function (sessionId, userId, userRole) {

this.id = sessionId;

this.userId = userId;

this.userRole = userRole;

};

this.destroy = function () {

this.id = null;

this.userId = null;

this.userRole = null;

};

})

Once a user is logged in, his information should probably be displayed somewhere (e.g. in the top-right corner). In order to do this, the user object must be referenced in the $scope object, preferably in a place that’s accessible to the entire application. While $rootScope would be an obvious first choice, I try to refrain from using $rootScope too much (actually I use it only for global event broadcasting). Instead my preference is to define a controller on the root node of the application, or at least somewhere high up in the DOM tree. The body tag is a good candidate:

<body ng-controller="ApplicationController">

...

</body>

The ApplicationController is a container for a lot of global application logic, and an alternative to Angular’s run function. Since it’s at the root of the $scope tree, all other scopes will inherit from it (except isolate scopes). It’s a good place to define the currentUser object:

.controller('ApplicationController', function ($scope,

USER_ROLES,

AuthService) {

$scope.currentUser = null;

$scope.userRoles = USER_ROLES;

$scope.isAuthorized = AuthService.isAuthorized;



$scope.setCurrentUser = function (user) {

$scope.currentUser = user;

};

})

We’re not actually assigning the currentUser object, we’re merely initializing the property on the scope so the currentUser can later be accessed throughout the application. Unfortunately we can’t simply assign a new value to it from a child scope, because that would result in a shadow property. It’s a consequence of primitive types (strings, numbers, booleans, undefined and null) being passed by value instead of by reference. To circumvent shadowing, we have to use a setter function. For a lot more on Angular scope and prototypal inheritance, read Understanding Scopes.

Besides initializing the currentUser property, I’ve also included some properties which allow easy access to USER_ROLES and the isAuthorized function. These should only be used in template expressions, not from other controllers, because doing so would complicate the controller’s testability. In the next chapter you’ll see how we use these properties.