Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 5/6: protect route (route guard) without redirecting to error route

I have a bit of a pickle. I am using Route guard (implementing CanActivate interface) to check if user is granted access to particular route:

const routes: Routes = [
    {
        path: '',
        component: DashboardViewComponent
    },
    {
        path: 'login',
        component: LoginViewComponent
    },
    {
        path: 'protected/foo',
        component: FooViewComponent,
        data: {allowAccessTo: ['Administrator']},
        canActivate: [RouteGuard]
    },
    {
        path: '**',
        component: ErrorNotFoundViewComponent
    }
];

Now it works great in protecting the '/protected/foo' route from activating, but I would like to tell the user that route he is trying to access is forbidden (similar to 403 Forbidden you may get from server).

The problem: How do I show the user this special error view without redirecting him to error route which seams to be the preferred option by so many sources I have found? And how do I still use my RouteGuard without actually loading the forbidden route, because if I check access inside my FooViewComponent and display different view it kind of defeats point of having RouteGuard in the first place.

Ideally I would like to have my RouteGuard not only returning false in canActivate() method, but also replace component completely with say ErrorForbiddenViewComponent. But I have no idea how to do it, or is it event possible. Any alternatives?

This is how my route guard looks now:

import {Injectable} from '@angular/core';
import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import {AuthService} from '../services/auth.service';

@Injectable()
export class RouteGuard implements CanActivate {

    constructor(
        private router: Router,
        private auth: AuthService
    ) {}

    canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        const { auth, router } = this;
        const { allowAccessTo } = next.data;
        const identity = auth.getIdentity();
        if (
            identity &&
            allowAccessTo.indexOf(identity.role)
        ) {
            // all good, proceed with activating route
            return true;
        }
        if (identity) {
            // TODO show ErrorForbiddenViewComponent instead of redirecting
            console.log('403 Forbidden >>', next);
        }
        else { 
            // not logged in: redirect to login page with the return url
            const [returnUrl, returnQueryParams] = state.url.split('?');
            console.log('401 Unauthorised >>', returnUrl, returnQueryParams, next);
            router.navigate(['/login'], {queryParams: {returnUrl, returnQueryParams}});
        }
        return false;
    }
}

So I am just preventing route from loading, but I am not redirecting. I only redirect non logged visitors to login route.

Reasoning:

  • Routes should reflect certain state of application - visiting a route url should recreate that state
  • To have error routes (except for 404 Not Found) would mean your application can actually recreate error states. This makes no sense as why would you keep error state as state of your application? For debugging purpose one should use logs (console or server), revisiting error page (i.e. page refresh) might interfere with that.
  • Also by redirecting to error route app should provide some insights of error to user. For that matter either some parameter would need to be passed via url or (far worse) keeping the error sate in some error service and retrieve it upon accessing error route.
  • Also, ignoring the RouteGuard and just loading the component and checking access inside it may result in some extra dependencies loaded which would not be used anyway (as user is not allowed), makes the whole lazy loading much harder.

Does anyone have some kind of solution for this? I also wonder how come that after Angular 2+ being around for so long nobody had this kind of situation before? Everybody is just ok with redirecting?

Also keep in mind that although I am currently using the FooViewComponent synchronously, that may change in future!

like image 601
Ivan Hušnjak Avatar asked May 22 '18 23:05

Ivan Hušnjak


People also ask

Which route Guard would you use to prevent a user from navigating to another view before saving their content?

Now here if we want to prevent navigation of an unauthorized user we can use CanActivate Guard, that will do the job but also download the module. Now to control the navigation as well as prevent downloading of that module we can use CanLoad Guard.

What is the difference between CanActivate and CanDeactivate in Angular?

CanActivateChild - Decides if children routes of a route can be activated. CanDeactivate - Decides if a route can be deactivated.

Which route Guard is helpful in preventing an authenticated access to a component?

CanActivate basically answers the question: “Does the user have access to this route?” We use this guard to prevent access to users who are not authorized to access a route.

Which route Guard is helpful in preventing Unauthorised access to a component in Angular?

Angular offers us the ultimate solution to address this problem. That is called AuthGuard. AuthGuard is used to protect the routes from unauthorized access.


1 Answers

I had once worked on the similar problem.

Sharing my stackblitz poc where I have created -

  • Authenticated Component (with guard)
  • Login Component
  • Permission Guard
  • Route (/auth route is provided with PermissionGuardService guard)

The guard is evaluating the user type and handling the redirection / error accordingly.

The use cases are -

  • User is not logged in (shows a toast with log in message)
  • User is not admin (shows a toast with unauthorised message)
  • User is admin (show a toast with success messaage)

I have stored the user in local storage.

EDIT - DEMO enter image description here

Let me know if you need a special handling in it and I will update the code base.

Cheers!

like image 81
planet_hunter Avatar answered Sep 29 '22 11:09

planet_hunter