0

I've got CanActivate guards on all my routes and they are failing over.

For instance, I have two guards:

export class AuthenticationGuard implements CanActivate {
    constructor(private as: AuthenticationService, private router: Router) { }

    canActivate() {
        if (this.as.isLoggedIn()) {
            return true;
        }

        this.router.navigate(['/login']);

        return false;
     }
    }

export class IsAdminGuard implements CanActivate {
    constructor(private us: UserService, private router: Router) { }

    canActivate() {
        if (this.us.isAdmin()) {
            return true;
        }

        this.router.navigate(['/home']);

        return false;
    }
}

And my route guards

const routes: Routes = [
    {
        path: 'home',
        component: DashboardComponent,
        canActivate: [AuthenticationGuard, IsAdminGuard]
    }
];

@NgModule({
    imports: [ RouterModule.forRoot(routes) ],
    exports: [ RouterModule ]
});

What's happening is that if AuthenticationGuard fails, it will sometimes check IsAdminGuard and instead of routing someone to /login when they are not authenticated, they are sent to /home which sends them to a different error page because they are not authenticated and are not an admin and the guards should have kicked them out on the first fail.

I can reproduce the issue 100% of the time if I remove the authentication jwt that AuthenticationService checks when it calls isLoggedIn(), refresh the /home route and can trace the AuthenticationGuard returning false and still calling IsAdminGuard

Here is the code for isLoggedIn()

isLoggedIn = () => {
    const token = this.get();
    const decoded = this.decode(token);

    return token && this.isNotExpired(decoded);
};  

decode = (token: string) => {
    const validToken = () => parts.length < 3
    const parts = token ? token.split('.') : '';

    if (!token || validToken()) {
        return '{}';
    }

    const payload = validToken() ? parts[1] : '{}';

    return JSON.parse(atob(payload));
};

private isNotExpired = (decodedToken) => this.getExpiryFrom(decodedToken) > new Date();

private getExpiryFrom = (decodedToken) => new Date(decodedToken.exp * 1000);

thoughts?

Gonçalo Peres
  • 11,752
  • 3
  • 54
  • 83
nbpeth
  • 2,967
  • 4
  • 24
  • 34
  • What happens if you change the order of the guards in the CanActivate Array? – Ben Steward Nov 27 '18 at 16:35
  • 1
    Is `isLoggedIn()` async? – Phix Nov 27 '18 at 16:37
  • @Phix it is synchronous - it just checks the expiry on the auth token – nbpeth Nov 27 '18 at 16:40
  • can you post it here just to cover all bases – Phix Nov 27 '18 at 16:43
  • @BenSteward that's hilarious, if I flip them it works fine. though that's not the logical order they should be operating in – nbpeth Nov 27 '18 at 16:44
  • It’s probably checking both, so even though the first fails, it is still hitting the second and then navigating to home. – Ben Steward Nov 27 '18 at 16:48
  • @Phix added the code for you – nbpeth Nov 27 '18 at 16:51
  • @BenSteward correct - adding logs to the two fail blocks of each guard shows they're still both getting called and returning false – nbpeth Nov 27 '18 at 16:52
  • 1
    FWIW, in my authguards I don’t do routing. I just call the logout function from my authservice, so routing is handled uniformly and any anomalies are cleaned up. – Ben Steward Nov 27 '18 at 16:54
  • That's a great suggestion that I can get behind. I'm still miffed as my understanding is that these shouldn't cascade - but I'm new to the angular – nbpeth Nov 27 '18 at 16:55
  • @BenSteward that did the trick. I pulled the routing logic down into authorization service. if you want to add a new answer or adjust yours a bit, I can check it off - thanks for the help, guys. – nbpeth Nov 27 '18 at 16:58

1 Answers1

1

Consider working in a short circuit to your isAdmin guard that rechecks the authentication status. That way the authentication truthiness will never be overridden by how your isAdmin truthiness turns out. It may seem redundant, but it would work.

Ben Steward
  • 2,338
  • 1
  • 13
  • 23
  • yeah I've tried that and it definitely works - though I did want to keep each guard atomic - was hoping to avoid coupling them together and maybe expose an underlying issue – nbpeth Nov 27 '18 at 16:53