AngularJS Authentication Patterns

12,336 views

Published on

There are so many options to implement basic and more advanced authentication techniques in AngularJS applications. Here's some food for thought on using the full potential of Angular including custom services, route resolvers and http interceptors to handle basic authentication, role based authentication authentication errors and a first approach to feature stripping based on user permissions.

Presented at the Seattle AngularJS Meetup on March 19, 2014 - http://www.meetup.com/AngularJS-SEA/events/169192362/

Published in: Technology

AngularJS Authentication Patterns

  1. 1. A U T H E N T I C AT I O N PAT T E R N S A N G U L A R J S
  2. 2. J o s h S c h u m a c h e r @ j o s h s c h u m a c h e r h t t p s : / / p l u s . g o o g l e . c o m / + J o s h S c h u m a c h e r s H a s O ff e r s
  3. 3. A U T H E N T I C AT I O N PAT T E R N S
  4. 4. I N I T I A L C O N S I D E R AT I O N S • HTTPS is a must • Trust no one • Even if you have awesome frontend security, do it on the backend too • Cookies vs Tokens • Token in params vs headers
  5. 5. routes = { '/' : { 'redirectTo' : '/login', 'public' : true }, ! '/about' : { 'templateUrl' : '/components/about/about.html', 'controller' : angular.noop, 'public' : true }, ! '/dashboard' : { 'templateUrl' : '/components/dashboard/dashboard.html', 'controller' : 'dashboard.dashboard' }, ! '/manage' : { 'templateUrl' : '/components/admin/manage.html', 'controller' : 'admin.manage', 'role' : ['admin'] }, ! '/login' : { 'templateUrl' : '/components/account/login.html', 'controller' : 'account.login', 'public' : true, 'resolve' : { data : ['authService', '$location', function(authService, $location) { if (authService.isLoggedIn) { $location.path('/dashboard'); } }] } } };
  6. 6. defaultResolvers.userAuthenticated = [ '$q', 'authService', function($q, authService) { var deferred = $q.defer(); ! var reject = function() { deferred.reject('login_required'); }; ! authService.getAuthPromise().then(function(result) { deferred.resolve(result); }, reject); ! return deferred.promise; } ];
  7. 7. _(routes).each(function(params, location) { params.location = location; params.caseInsensitiveMatch = true; params.reloadOnSearch = false; ! var resolve = angular.extend({}, defaultResolvers, params.resolve || {}); ! if (params.public) { delete resolve.userAuthenticated; } ! // Add role based resolver if (params.role) { resolve.role = ['$q', 'rolesService', function($q, rolesService) { var deferred = $q.defer(); ! if (rolesService.hasRoles(params.role)) { deferred.resolve(); } else { deferred.reject('missing_role'); } }]; } ! params.resolve = resolve; ! $routeProvider.when(location, params); });
  8. 8. // handle logout $routeProvider.when('/logout', { resolve : { data : ['authService', function(authService) { authService.logout(); }] } }); ! // otherwise we redirect everything to login $routeProvider.otherwise({ 'redirectTo' : '/login', });
  9. 9. $rootScope.$on('$routeChangeError', function(event, current, previous, rejection) { switch (rejection) { case 'login_required': authService.logout(); break; default: $log.error('router', 'Unhandled route resolver rejection', rejection); } });
  10. 10. app.config(function ($httpProvider) { $httpProvider.interceptors.push(['$rootScope', function ($rootScope) { return { responseError : function(response) { if (response.status === 401) { $rootScope.$broadcast('http_status_401'); } ! return $q.reject(response); } } }]); });
  11. 11. U S I N G I N T E R C E P T O R S T O S C R U B F E AT U R E S <div data-keep="account:manage"> Some sort of feature that you need the account->manage permission to see/use </div>
  12. 12. app.config(function ($httpProvider) { $httpProvider.interceptors.push(['$injector', function ($injector) { return { response : function(response) { if (response.headers('content-type') === 'text/html' || response.config.url.match(/.html$/)) { var userPermissions = $injector.get('userPermissions'); ! var $responseData = $(response.data); ! $responseData.find('[data-keep]').each(function() { if (!userPermissions.hasPermission($(this).data('keep'))) { $(this).remove(); } else { $(this).removeAttr('data-keep'); } }); ! // taking special care to do outerHTML so that scripts aren't lost; // doing $('<div>').append($responseData).html() ends up stripping out scripts in HTML views var html = []; _.each($responseData, function($el) { if ($el && $el.outerHTML) { html.push($el.outerHTML); } }); response.data = html.join(''); } return response; } } } }]); });
  13. 13. A F E W TA K E A WAY S • Maintain application auth state in a service • Route Resolvers • HTTP Interceptors • Handle authentication errors • Feature stripping

×