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!
ComponentFactory
as described in blog.mgechev.com/2015/09/30/… and replace the component itself with aAccessForbidden
one – Needlefish