keycloak-angular
keycloak-angular copied to clipboard
Feature: support CanLoad and CanActivateChild implementation in KeycloakAuthGuard
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, 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!
Ditto, this is actually the other most important use case of angular routing.
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:
- point to our auth guard in the respective angular route definition
canLoad: [AuthGuard],
loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
- 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);
}
});
}
}
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 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.
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.
It's still breaking if I use CanActivateChild implementation in KeycloakAuthGuard.!
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);
}
}