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
withngRoute
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 namedauth
. - 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
) hasauthRequired
set totrue
. - 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.