Author Image

Ruben Smeets

Developer

Quiet but effective in the areas of Laravel, Angular and Ionic. Builds the most beautiful things on Partyraiser's beats. Has taken his first steps on the writer's path, something that suits him well.

Blog Header

Looking for how you can protect certain routes in Angular for authenticated users, but did not find anything that matches your problem? We encountered the same problem and came up with a solution ourselves. Of course we will share the code with you.

Protect routes

Recently, we have developed a project in which we work with users who have different roles and the corresponding authorisations. Some users may not be allowed to access specific information or perform certain actions. Others only up to a certain level. We looked for the question; how can we protect routes for users with different rights?

Various solutions can be found on the internet, but not as an overall solution. That is why we came up with our own solution.

Solution for the customer

In this blog we will only show the solution on the client side. The backend is built with Laravel and will not be discussed in detail. The backend has roles and permissions that can be linked to users. These can be assigned on the client side. The backend returns an object with all necessary data when the client side requests it.

Four steps

The following four components are discussed:

1. LoginGuard: Checks whether the user is logged in.
2. PermissionService: Checks whether the user has the correct permission
3. PermissionGuard: Checks whether the user has the correct permission to access a relevant URL
4. App routing module: Each route contains a permission that allows the user to navigate to the relevant url.

Step 1- Login Guard

Because our system can only be accessed by logged-in users, we use a guard. This guard is called every time a visitor of the system wants to navigate to a URL. In the routing file we specify when the login guard should be called.

// CanActivate for Login guard on the highest route.
canActivate: [LoginGuard]

// CanActivate function in login.guard.ts

The canActivate is set on the highest possible route. This canActivate now also applies to all sub-routes.

The canActivate method has been written out in the Login guard.

--
    canActivate(next: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): Observable<boolean> {
        return this.authService.isLoggedIn();
    }


--

// CanActivate function in login.guard.ts

As you can see, the LoginGuard checks with the authentication service whether the user is logged in. For this, a simple request is made to the backend to ask whether the current user is logged in. The backend returns a boolean value to this question.

Gard for multiple routes
In the routing file (Step 4), each route individually indicates whether the guard should be activated before the user wants to navigate to an url. Since our system is only accessible to logged-in users, we apply the guard to a larger group of routes. We specify the guard for a number of routes at the same time instead of specifying the guard on each route.

Step 2 - PermissionService

The system can check if the user is logged in, but this is not enough. We want to make certain routes accessible for certain users through different permissions.

When the backend sends the permissions of the logged in user to the client sides, the PermissionService will read the permissions and check whether the requested URL can be accessed by the user. For each url in our routing file the required permission is indicated.

--

hasPermission(component): Observable<boolean> {
        return this.authService.isLoggedIn().map(res => {
  // Get permissions from user object.
            this.permissions = this.getPermissions();
  // Check if user object has permissions to access the current component.
            return this.checkPermission(component.data.permission);
        });
    }

--

// HasPermisson function in permission.service.ts

Step 3 - PermissionGuard

As with the LoginGuard, the PermissionGuard is also called when the logged in user wants to navigate to an url. The guard uses the PermissionService to check whether the user has the correct permissions.

--

canActivate(next: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        return this.permissionService.hasPermission(next);
    }

--

// CanActivate function in permission.guard.ts

Step 4 - Routing Module

With each url it is specified which permission is required to navigate to the url. The PermissionGuard is also specified by calling the `canActivate` with each url with the PermissionGuard guard.

The permission is also given by means of the optional data parameter. In this optional parameter, an object with key and value can be specified. Through the ActivatedRouteSnapshot (from Angular's framework), the PermissionGuard can read this optional data. Instead of reading the data in the guard, it is passed on to the Permission Service. The service now also has the possibility to access the optional data and check whether the user has the correct permission.

--

{
                path: 'shipment',
                children: [
                    {
                        path: '',
                        component: ShipmentListComponent,
                        canActivate: [PermissionGuard],
    // Permission which is need to access this component. 
    // Permission checked by Permission Guard
                        data: {permission: 'list-shipment'}
                    },
                    {
                        path: 'new',
                        component: ShipmentEditComponent,
                        canActivate: [PermissionGuard],
// Permission which is need to access this component. 
    // Permission checked by Permission Guard
                        data: {permission: 'add-shipment'}
                    },

.
.
.
  // CanActivate for Login guard on the highest route.
canActivate: [LoginGuard]

--

// Example routes with guards in app-routing.module.ts

Of course, the implementation depends on your system and the functionality you need. Do you have a question or a better solution? Let us know.