Protecting routes using authentication guards

Loading

In AngularJS, protecting routes ensures that only authenticated users can access certain parts of your application. You can implement these “authentication guards” by intercepting route changes and verifying the user’s authentication status before allowing the route transition. There are several common approaches:

  • Using Route Resolve Functions: Configure routes to include a resolve property that checks authentication before instantiating the controller.
  • Using Global Route Change Events: Listen for route change events (e.g., $routeChangeStart with ngRoute or $stateChangeStart with UI-Router) to enforce access control.

Below, we detail these approaches with examples.


1. Protecting Routes with Resolve Functions

How It Works

When configuring routes with $routeProvider (or with UI-Router’s $stateProvider), you can include a resolve block. This block is a collection of promises that must be resolved before the route loads. In the resolve function, you can check whether the user is authenticated and, if not, redirect to the login page.

Example Using ngRoute

a. Define the Authentication Service

Create a service that handles login status (e.g., by checking for a stored token):

angular.module('myApp')
.service('AuthService', function ($window) {
this.isAuthenticated = function () {
// For simplicity, we assume a token in localStorage means authentication.
// In a real app, you might verify token expiration or use more complex logic.
return !!$window.localStorage.getItem('token');
};

this.logout = function () {
$window.localStorage.removeItem('token');
};
});

b. Configure Routes with Resolve

In the route configuration, add a resolve property that checks authentication:

angular.module('myApp')
.config(function ($routeProvider) {
$routeProvider
.when('/dashboard', {
templateUrl: 'dashboard.html',
controller: 'DashboardController',
resolve: {
auth: function (AuthService, $location) {
if (!AuthService.isAuthenticated()) {
// Redirect to login if not authenticated
$location.path('/login');
return false; // Resolve rejection prevents route loading
}
// Return true or a promise that resolves if authentication succeeds
return true;
}
}
})
.when('/login', {
templateUrl: 'login.html',
controller: 'LoginController'
})
.otherwise({
redirectTo: '/login'
});
});

In this configuration:

  • The dashboard route uses a resolve property named auth.
  • The resolve function checks authentication via AuthService.isAuthenticated().
  • If the user is not authenticated, the function redirects to the /login route and prevents the dashboard from loading.

c. Dashboard Controller

Now, create the dashboard controller as usual:

angular.module('myApp')
.controller('DashboardController', function ($scope) {
// Controller logic for authenticated users
$scope.message = "Welcome to your dashboard!";
});

2. Protecting Routes with Global Route Change Events

Another method involves listening to route change events on $rootScope. This is especially useful when you want to enforce authentication across many routes without repeating the resolve logic in every route configuration.

Example Using $routeChangeStart

a. Configure Global Event Listener

In your main application run block, add a listener to intercept route changes:

angular.module('myApp')
.run(function ($rootScope, $location, AuthService) {
$rootScope.$on('$routeChangeStart', function (event, next, current) {
// Assume routes that require authentication have a custom property 'authRequired'
if (next.$$route && next.$$route.authRequired && !AuthService.isAuthenticated()) {
// Prevent the route change and redirect to login
event.preventDefault();
$location.path('/login');
}
});
});

b. Mark Routes as Requiring Authentication

In your route configuration, add a custom property (e.g., authRequired) to routes that need protection:

angular.module('myApp')
.config(function ($routeProvider) {
$routeProvider
.when('/dashboard', {
templateUrl: 'dashboard.html',
controller: 'DashboardController',
authRequired: true // Custom property indicating the route is protected
})
.when('/login', {
templateUrl: 'login.html',
controller: 'LoginController'
})
.otherwise({
redirectTo: '/login'
});
});

With this setup:

  • Before each route change, $routeChangeStart checks if the target route (next.$$route) has authRequired set to true.
  • If so, and if AuthService.isAuthenticated() returns false, the event is prevented and the user is redirected to the login page.

UI-Router Alternative

If you are using UI-Router, you can similarly listen to the $stateChangeStart event:

angular.module('myApp')
.run(function ($rootScope, $state, AuthService) {
$rootScope.$on('$stateChangeStart', function (event, toState) {
if (toState.data && toState.data.authRequired && !AuthService.isAuthenticated()) {
event.preventDefault();
$state.go('login');
}
});
});

And then define states with an authRequired flag:

angular.module('myApp')
.config(function ($stateProvider) {
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: 'dashboard.html',
controller: 'DashboardController',
data: { authRequired: true }
})
.state('login', {
url: '/login',
templateUrl: 'login.html',
controller: 'LoginController'
});
});

3. Best Practices for Route Protection

  • Centralize Authentication Logic: Keep your authentication checks within a dedicated service (e.g., AuthService) to avoid duplicating logic.
  • Use Resolve When Possible: Resolve functions ensure that your route does not load until authentication checks are complete.
  • Leverage Global Route Events: For global security policies, listen to $routeChangeStart or $stateChangeStart events.
  • Handle Token Expiration: Ensure that your backend API returns appropriate error codes (e.g., 401) when a token is invalid or expired, and handle these cases gracefully on the client side.
  • Redirect on Logout: Ensure that when a user logs out, all protected routes are inaccessible until the user logs in again.

Leave a Reply

Your email address will not be published. Required fields are marked *