Skip to content

Instantly share code, notes, and snippets.

@diyfr
Created June 30, 2020 09:42
Show Gist options
  • Save diyfr/8baed8275a0fe0e9332f8255cfb33d7d to your computer and use it in GitHub Desktop.
Save diyfr/8baed8275a0fe0e9332f8255cfb33d7d to your computer and use it in GitHub Desktop.
Angular & Keycloak

Ajout de la dépendance:

npm install --save keycloak-angular@latest
npm install --save [email protected]  

Editez app.module, pour ajouter le provider et remplacez bootstrap par entryComponents, ajout de la méthode keycloakService.init environment.settings.keycloakJsonUrl contient l'emplacement relatif du fichier keycloak.json généré à partir de keycloak ex : ./assets/config/keycloak.json

import { KeycloakService, KeycloakAngularModule } from 'keycloak-angular';

const keycloakService = new KeycloakService();

@NgModule({
  imports: [KeycloakAngularModule],
  providers: [
    {
      provide: KeycloakService,
      useValue: keycloakService
    }
  ],
  entryComponents: [AppComponent]  /*<- ne pas oublier la modification ici */
})

export class AppModule {
  ngDoBootstrap(app: ApplicationRef) {
    Logger.i('[AppModule] ngDoBootstrap');
    keycloakService
      .init({
        config: environment.settings.keycloakJsonUrl,
        loadUserProfileAtStartUp: false, /* Important car REALM/account est desactivé */
        enableBearerInterceptor: true,
        bearerPrefix: 'Bearer',  /* Important voir la RFC 6750*/
        bearerExcludedUrls: ['/assets']
      })
      .then(() => {
        Logger.i('[AppModule] Keycloak init success');
        app.bootstrap(AppComponent);
      })
      .catch(error => Logger.e('[AppModule] Keycloak init failed', error));
  }
}

Ajout du AuhtGuard (routage)

ng g auth auth/auth
ng g guard auth/auth

Modifiez le fichier auth/auth-guard.ts généré par celui joint à ce snippet

Editez votre module de routage pour y protéger vos routes :

const routes: Routes = [
  {
    path: "",
    redirectTo: "/home",
    pathMatch: 'full'
  },
  {
    path: "home",
    component: HomeComponent
  },
  {
    path: "secure",
    component: SecureComponent,
    canActivate: [AuthGuard]
  },
  {
    path: "admin",
    component: AdminComponent,
    data: {
      roles: ["ADMIN_APP"] // Ici le jeton Keycloak devra contenir le rôle ADMIN_APP
    },
    canActivate: [AuthGuard],
  }
];

Il faut s'abonner pour être notifié du refus par un rôle manquant

this.authGuard.onRoleError().subscribe(() => {
      this.showMessage();
})

Récupération du profile de l'utilisateur

this.keycloakService.loadUserProfile().then((profile) => {
  console.log(profile);
})

S'abonner aux événements de Keycloak

this.keycloakService.keycloakEvents$.subscribe(e => {
      console.log(e);
})

fichier auth-guard.ts

import { Injectable, Output } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
import { Subject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard extends KeycloakAuthGuard implements CanActivate {
  private _roleError: Subject<any> = new Subject();
  private roleError$ = this._roleError.asObservable();
  constructor(protected router: Router, protected keycloakAngular: KeycloakService) {
    super(router, keycloakAngular);
  }

  public onRoleError(): Observable<any> {
    return this._roleError.asObservable();
  }
  isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
    return new Promise((resolve, reject) => {
      if (!this.authenticated) {
        this.keycloakAngular.login()
          .catch(e => console.error(e));
        return reject(false); // throw Error: Uncaught (in promise): An error happened during access validation. Details:false in console 
      }

      const requiredRoles: string[] = route.data.roles;
      if (!requiredRoles || requiredRoles.length === 0) {
        return resolve(true);
      } else {
        if (!this.roles || this.roles.length === 0) {
          this._roleError.next(null);
          resolve(false);
        }
        if (requiredRoles.every(role => this.roles.indexOf(role) > -1)) {
          resolve(true);
        } else {
          this._roleError.next(null);
          resolve(false);
        }

      }
    });
  }
}

Utilisation en dehors des routes (sur un composant en particulier)

  public connectedUser = false;
  
  constructor(private keycloakService: KeycloakService) {
  }

  ngOnInit(): void {
    this.keycloakService.keycloakEvents$.subscribe(e => {
      Logger.i(e.type);
      switch (e.type) {
        case KeycloakEventType.OnAuthSuccess:
        case KeycloakEventType.OnReady:
          this.connectedUser = true;
          break;
        case KeycloakEventType.OnAuthError:
        case KeycloakEventType.OnAuthLogout:
        case KeycloakEventType.OnAuthRefreshError:
        case KeycloakEventType.OnAuthRefreshSuccess:
        case KeycloakEventType.OnTokenExpired:
        default:
          this.connectedUser = false;
          break;
      }
    });
  }

  login() {
    this.keycloakService.login().then((e) => {
    }).catch((e) => {
      Logger.e(e);
    });
  }
  logout() {
    this.connectedUser = false;
    this.keycloakService.logout();
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment