keycloak-angular icon indicating copy to clipboard operation
keycloak-angular copied to clipboard

Feature: support CanLoad and CanActivateChild implementation in KeycloakAuthGuard

Open NehalDamania opened this issue 6 years ago • 8 comments

Bug Report or Feature Request (mark with an x)

- [ ] bug report -> please search for issues before submitting
- [x ] feature request

Versions.

Repro steps.

The log given by the failure.

Desired functionality.

Can we extend KeycloakAuthGuard to support CanLoad and CanActivateChild.

This will be very useful for lazy routed modules.

NehalDamania avatar Jul 18 '18 10:07 NehalDamania

@NehalDamania, I will take a look at your feature request. I'm pretty busy last days, so as soon as I have some time available I will send you a feedback. Thanks!

mauriciovigolo avatar Jul 24 '18 15:07 mauriciovigolo

Ditto, this is actually the other most important use case of angular routing.

igorlino avatar May 06 '20 10:05 igorlino

My current workaround/solution, for whoever needs it. @mauriciovigolo if you would like a MR, let me know.

Assuming that a route uses lazy load:

  1. point to our auth guard in the respective angular route definition
       canLoad: [AuthGuard],
        loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
  1. implement CanLoad in the AuthGuard
@Injectable()
export class AuthGuard extends KeycloakAuthGuard implements CanLoad {

    constructor(protected router: Router, protected keycloakAngular: KeycloakService) {
        super(router, keycloakAngular);
    }

    canLoad(route: Route, segments: UrlSegment[]): boolean | Observable<boolean> | Promise<boolean> {
        return new Promise(async (resolve, reject) => {
            try {
                this.authenticated = await this.keycloakAngular.isLoggedIn();
                this.roles = await this.keycloakAngular.getUserRoles(true);

                const result = await this.checkAccessAllowed(route.data);
                resolve(result);
            } catch (error) {
                reject('An error happened during access validation. Details:' + error);
            }
        });
    }

    isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
        return this.checkAccessAllowed(route.data);
    }

    checkAccessAllowed(data: Data): Promise<boolean> {
        return new Promise(async (resolve, reject) => {
            if (!this.authenticated) {
                this.keycloakAngular.login().catch(e => console.error(e));
                return reject(false);
            }
            const requiredRoles = data as string[];
            if (!requiredRoles || requiredRoles.length === 0) {
                return resolve(true);
            } else {
                if (!this.roles || this.roles.length === 0) {
                    resolve(false);
                }
                let granted = false;
                for (const requiredRole of requiredRoles) {
                    if (this.roles.indexOf(requiredRole) > -1) {
                        granted = true;
                        break;
                    }
                }
                resolve(granted);
            }
        });
    }
}

igorlino avatar May 06 '20 10:05 igorlino

Thank for your work @igorlino.

However, with the latest version of Angular (v11), this does not to seem to work because the getUserRoles method of keycloakAngular must be runned after the initialization of the keycloak service (with the init method). I suggest to initialize keycloak service at the beginning of the canLoad method

return this.keycloakService.init(
        // your config
).then(// your code)

or use NgRx actions with a combineLatest to be sure that the authentication will be successful.

return combineLatest([this.actions$.pipe(ofType(µNgAuthenticationIndexAuthenticationSuccessful))])
.pipe(// Your code)

collettemathieu avatar Apr 22 '21 09:04 collettemathieu

@collettemathieu I'm using Angular 11.2.7 at the moment and I have no problems but what you say makes also sense. I will give it a try when I get to update to Angular 12.

igorlino avatar Apr 22 '21 17:04 igorlino

Thank for your reply @igorlino.

Perhaps I have this case because I load the keycloak service in an external NX library and not directly in the app.module.ts.

Keep me posted for Angular 12.

collettemathieu avatar Apr 23 '21 07:04 collettemathieu

It's still breaking if I use CanActivateChild implementation in KeycloakAuthGuard.!

Mallesh-Nagothi avatar Mar 05 '22 21:03 Mallesh-Nagothi

Got the same problem. It would be really a useful feature. UPD: actually, CanActivateChild works fine:

canActivate: [CustomKeycloakAuthGuard],
canActivateChild: [CustomKeycloakAuthGuard],
loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)

And in your custom guard you just add the canActivateChild method:

export class CustomKeycloakAuthGuard extends KeycloakAuthGuard implements CanActivateChild  {
  constructor(
    protected readonly router: Router,
    protected readonly keycloak: KeycloakService
  ) {
    super(router, keycloak);
  }

  public async isAccessAllowed(   
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ) {
*lib code here*
}

  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    return this.isAccessAllowed(childRoute, state);
  }
}

sovushka-utrom avatar Mar 31 '22 10:03 sovushka-utrom