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

Support for Pure Function-Based Guards in Angular

Open nephi5 opened this issue 2 years ago • 2 comments

Bug Report or Feature Request (mark with an x)

Currently, 'keycloak-angular' is functioning as expected with Angular versions up to v.17. However, the reliance on Class-based Guards is a concern due to Angular's evolving standards


- [x] feature request

Versions.

{
"keycloak-angular": "^14.0.0",
"@angular/core": "^17.0.0",
"keycloak-js": "^18.0.0"
}

Repro steps.

  • As of Angular 15.2.0 they have introduced the deprecation of Class and InjectionToken guards and resolvers. Guards should now be written as plain JavaScript function. Link to deprecation commit
  • Currently when upgrading from Angular v.15 to v.16 the ng update automatically wraps the Class based Guards into mapToCanActivate function provided under @angular/router.

Desired functionality.

  • To ensure the longevity and compatibility of 'keycloak-angular' with future Angular releases, it would be beneficial to transition to a pure function-based solution for Guards. This change is not about fixing a current issue but about aligning with the evolving Angular ecosystem and avoiding potential future incompatibilities.

nephi5 avatar Nov 17 '23 15:11 nephi5

Hi @nephi5, I agree with the change request. The new release for Angular v.17 will not have this change yet, but I will schedule it to change it soon.

mauriciovigolo avatar Nov 20 '23 05:11 mauriciovigolo

I'm using the function-based guard for a half year now, you could do it like follows:

import { CanMatchFn, Router, UrlTree } from '@angular/router';
import { inject } from '@angular/core';

// Services
import { KeycloakService } from 'keycloak-angular';

export const authGuard: CanMatchFn = async (route, segments): Promise<boolean | UrlTree> => {
  const router = inject(Router);
  const keycloakService = inject(KeycloakService);

  const authenticated: boolean = await keycloakService.isLoggedIn();

  if (!authenticated) {
    await keycloakService.login({
      redirectUri: window.location.origin,
    });
  }

  // Get the user Keycloak roles and the required from the route
  const roles: string[] = keycloakService.getUserRoles(true);
  const requiredRoles = route.data?.['roles'];

  // Allow the user to proceed if no additional roles are required to access the route
  if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) {
    return true;
  }

  // Allow the user to proceed if ALL of the required roles are present
  const authorized = requiredRoles.every((role) => roles.includes(role));
  // Allow the user to proceed if ONE of the required roles is present
  //const authorized = requiredRoles.some((role) => roles.includes(role));

  if (authorized) {
    return true;
  }

  // Display my custom HTTP 403 access denied page
  return router.createUrlTree(['/access']);
};

ThoSap avatar Jan 29 '24 07:01 ThoSap