Écrit par Etienne R.
Dans cette seconde partie, nous allons mettre en place une application front avec Angular. Sur la page d’accueil nous aurons un lien nous invitant à nous connecter à un dashboard. Cette page sera accessible uniquement aux personnes connectées. Pour ce faire, nous allons utiliser une librairie Angular afin de nous mâcher le travail.
Remarque : libre à vous d’utiliser l’API de Keycloak nativement et sans passer par la mire d’authentification de Keycloak.
Création du client dédié
Dans l’administration de Keycloak, créez un nouveau client, en cliquant sur “Clients” puis sur le bouton “Create client”. Dans le sélecteur “Client type”, laissez la valeur “OpenID Connect” puis définissez un id quelconque dans le champ “Client ID” et validez en cliquant sur le bouton “Next”.
Puis cliquez de nouveau sur le bouton “Next”. Dans les champs “Root URL”, “Valid redirect URIs” et “Web origins”, définissez l’URL de base par défaut d’Angular, c’est-à-dire “http://localhost:4200”. Dans le champ “Valid post logout redirect URIs” saisissez “http://localhost:4200/logout“ et cliquez sur le bouton “Save”.
Test de connexion avec CURL
Avec l’utilisateur créé précédemment, on peut tester la récupération du token avec la requête CURL ci-dessous.
curl -s -X POST http://localhost:8080/realms/ouidou/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=felicie.tations@ouidou.fr&password=<password>&client_id=angular-ouidou" | jq
On récupère bien un “access_token” autrement dit, un JWT (JSON Web Token) avec les données de notre utilisateur (email, nom, rôles, etc…). Mais aussi d’autres informations intéressantes telles que le temps d’expiration en millisecondes du token mais également du token refresh (“refresh_token”).
Changer le temps du token
Par défaut, il est de 5 minutes (300 ms). Pour le changer, cliquez sur le client concerné puis sur l’onglet “Advanced”. Allez dans la partie “Access Token Lifespan”. Dans le menu déroulant, sélectionnez “Expires in” puis indiquez une valeur en minute(s), en heure(s) ou en jour(s) puis validez en cliquant sur le bouton “Save”.
Installation et configuration d’Angular
Au préalable, vérifiez que Node et NPM sont bien installés sur votre machine de développement.
$ node -v && npm -v
v22.13.1
10.9.2
Afin de lancer la commande d’installation suivante npx -p @angular/cli ng new angular-ouidou
.
Remarque : la version d’Angular utilisée lors de la rédaction de cet article est la 19.1.
Puis placez-vous dans le répertoire de l’application Angular afin d’y installer les 2 librairies supplémentaires “keycloak-angular“ et “keycloak-js”.
cd angular-ouidou && npm install keycloak-angular keycloak-js
Remarque : les versions des librairies utilisées lors de la rédaction de cet article sont respectivement 19.0.2 et 26.1.0.
Variables d’environnements
Avant de poursuivre, on génère les fichiers d’environnement pour y stocker les valeurs liées à Keycloak.
$ npx ng generate environments
CREATE src/environments/environment.ts (31 bytes)
CREATE src/environments/environment.development.ts (31 bytes)
Dans le fichier “environment.development.ts”, on saisit nos variables (l’URI de Keycloak, l’adresse de redirection, l’adresse de redirection du logout, le realm et le clientId).
export const environment = {
production: false,
title: 'Ouidou Corp',
keycloak: {
uri: 'http://localhost:8080',
redirectUri: 'http://localhost:4200/dashboard',
redirectUriLogout: 'http://localhost:4200/logout',
realm: 'ouidou',
clientId: 'angular-ouidou',
},
};
Dans le fichier “environment.ts”, on laisse les variables à vide.
export const environment = {
production: true,
title: '',
keycloak: {
uri: '',
redirectUri: '',
redirectUriLogout: '',
realm: '',
clientId: '',
},
};
Dans le fichier “tsconfig.json”, dans l’option “CompilerOptions”, ajoutez un alias pour le dossier des environnements.
"paths": {
"@environments/*": ["./src/environments/*"]
}
Puis démarrez le serveur de développement avec la commande npm start.
Instancier la librairie Keycloak-Angular
Commençons par générer le fichier de configuration pour Keycloak avec la commande ci-dessous.
$ npx ng g class init/keycloak.config --type=factory --skip-tests
CREATE src/app/init/keycloak.config.ts (22 bytes)
Afin d’instancier la configuration de Keycloak en mode “check-sso”.
import { environment } from '@environments/environment.development';
import { provideKeycloak } from 'keycloak-angular';
export const provideKeycloakAngular = () =>
provideKeycloak({
config: {
url: environment.keycloak.uri,
realm: environment.keycloak.realm,
clientId: environment.keycloak.clientId,
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
redirectUri: environment.keycloak.redirectUri,
},
});
Puis créez le fichier “silent-check-sso.html” dans le dossier “src/assets”.
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
Afin que ce dossier soit prit en compte par Angular, modifiez le fichier “angular.json”.
"assets": [
"src/assets"
],
On en profite également pour appeler le framework CSS, Pico CSS.
"styles": [
"https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css",
"src/styles.css"
],
Puis dans le fichier “app.config.ts”, importez le module “provideKeycloakAngular” comme provider.
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideKeycloakAngular } from './keycloak.config';
export const appConfig: ApplicationConfig = {
providers: [
provideKeycloakAngular(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
],
};
Composants
Hormis la mire de connexion gérée par Keyloak, on va avoir besoin de composants tels que les pages d’accueil (“home”), du dashboard et de déconnexion (“logout”) ainsi que le composant de navigation.
Home
On commence donc par générer le composant de la page d’accueil avec la commande dédiée.
$ npx ng generate component home --skip-tests --inline-template --style none
CREATE src/app/home/home.component.ts (187 bytes)
import { Component, inject, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { environment } from '@environments/environment';
@Component({
selector: 'app-home',
standalone: true,
template: `<p>Vous êtes actuellement sur la page d'accueil.</p>`,
})
export class HomeComponent implements OnInit {
private readonly titleService = inject(Title)
ngOnInit() {
this.titleService.setTitle(`Accueil | ${environment.title}`);
}
}
Dashboard
Puis le composant de la page (bientôt protégée) du dashboard.
$ npx ng generate component dashboard --skip-tests --inline-template --style none
CREATE src/app/dashboard/dashboard.component.ts (202 bytes)
import { Component, inject, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { environment } from '@environments/environment';
@Component({
selector: 'app-dasboard',
standalone: true,
template: `<p>Ceci est votre espace protégé.</p>`,
})
export class DashBoardComponent implements OnInit {
private readonly titleService = inject(Title);
ngOnInit() {
this.titleService.setTitle(`Dashboard | ${environment.title}`);
}
}
Logout
Puis la page de déconnexion qui informe l’utilisateur de sa déconnexion.
$ npx ng generate component logout --skip-tests --inline-template --style none
CREATE src/app/logout/logout.component.ts (190 bytes)
import { Component } from '@angular/core';
@Component({
selector: 'app-logout',
template: `<p>Vous êtes désormais déconnecté(e)</p>`,
})
export class LogoutComponent {}
Navigation
On termine avec le composant de navigation qui va comporter de la logique car on a besoin de conditionner l’affichage en fonction de l’état de déconnexion (affichage par défaut) ou de connexion.
$ npx ng generate component navigation --skip-tests --style none
CREATE src/app/navigation/navigation.component.html (26 bytes)
CREATE src/app/navigation/navigation.component.ts (191 bytes)
Ci-dessous le contenu du fichier “navigation.component.ts”.
import { Component, inject, OnInit } from '@angular/core';
import { RouterModule } from '@angular/router';
import { environment } from '@environments/environment';
import Keycloak from 'keycloak-js';
@Component({
selector: 'app-navigation',
standalone: true,
imports: [RouterModule],
templateUrl: './navigation.component.html'
})
export class NavigationComponent implements OnInit {
private readonly keycloak = inject(Keycloak);
userName?: string;
get isLoggedIn(): boolean {
return this.keycloak.authenticated ?? false;
}
ngOnInit(): void {
if (this.isLoggedIn) {
this.keycloak
.loadUserProfile()
.then((profile) => (this.userName = profile.username));
}
}
redirectToLoginPage(): void {
this.keycloak.login();
}
logout(): void {
this.keycloak.logout({
redirectUri: environment.keycloak.redirectUriLogout
});
}
}
Et son template (“navigation.component.html”), on affiche le menu de navigation conditionné.
<nav>
<ul>
<li><strong>Ouidou Corp</strong></li>
</ul>
<ul>
<li>
<a
routerLink="/"
routerLinkActive="active"
ariaCurrentWhenActive="page"
[routerLinkActiveOptions]="{ exact: true }"
>Accueil</a
>
</li>
@if(!isLoggedIn) {
<li>
<a href="javascript:void(0)" (click)="redirectToLoginPage()">Connexion</a>
</li>
} @else {
<li>
<a
routerLink="/dashboard"
routerLinkActive="active"
ariaCurrentWhenActive="page"
>Dashboard</a
>
</li>
<li>
<details class="dropdown">
<summary>
{{ userName }}
</summary>
<ul dir="rtl">
<li>
<a href="javascript:void(0)" (click)="logout()">Déconnexion</a>
</li>
</ul>
</details>
</li>
}
</ul>
</nav>
On importe ce composant dans le composant parent “app.component.ts”.
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavigationComponent } from "./navigation/navigation.component";
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, NavigationComponent],
templateUrl: './app.component.html',
})
export class AppComponent {}
Et on met à jour le template “app.component.html”.
<div class="container">
<app-navigation />
<router-outlet />
</div>
Routes
Nos composants sont prêts, il ne nous reste plus qu’à mettre en place le routing de notre application.
Guard
Mais avant de ce faire, il faut créer le guard de Keycloak pour les futures pages accessibles uniquement pour les utilisateurs connectés.
$ npx ng generate guard keycloak --skip-tests
✔ Which type of guard would you like to create? CanActivate
CREATE src/app/keycloak.guard.ts (133 bytes)
import { inject, Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import Keycloak from 'keycloak-js';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class KeycloakGuard implements CanActivate {
private readonly keycloak = inject(Keycloak);
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
if (this.keycloak.authenticated) {
return true;
}
this.keycloak.redirectUri;
return false;
}
}
Cela fait beaucoup de lignes de code, mais pour résumer, si l’utilisateur est connecté, il poursuit sa navigation. Sinon, il est redirigé sur la page de connexion Keycloak.
Déclaration des routes
Ainsi, on peut mettre à jour le fichier de routing avec la route d’accueil et celle du dashboard dans le fichier “app.routes.ts”.
import { Routes } from '@angular/router';
import { DashBoardComponent } from './dashboard/dashboard.component';
import { HomeComponent } from './home/home.component';
import { KeycloakGuard } from './keycloak.guard';
import { LogoutComponent } from './logout/logout.component';
export const routes: Routes = [
{ path: '', component: HomeComponent},
{ path: 'logout', component: LogoutComponent},
{
path: 'dashboard',
component: DashBoardComponent,
canActivate: [KeycloakGuard],
},
];
Restriction par rôle
On a désormais une connexion fonctionnelle et une application en partie protégée. Pour aller plus loin, on veut restreindre l’accès à la future partie “managers” pour les utilisateurs disposant du rôle “view-managers”.
Configuration Keycloak
Dans l’administration de Keycloak, allez dans “Realm roles” puis cliquez sur le bouton “Create role”. Dans le champ “Role name”, saisissez “view-managers” et sauvegardez en cliquant sur le bouton “Save”.
Pour attribuer ce rôle au groupe “managers”, cliquez sur “Groups” puis sur “managers”. Dans l’onglet “Role mapping”, cliquez sur le bouton “Assign role”. Dans la liste déroulante, sélectionnez “Filter by realm roles” afin de sélectionner le rôle “view-managers” et validez en cliquant sur le bouton “Assign”.
Remarque : sur Keycloak, il est également possible d’ajouter un rôle à un utilisateur sans passer par un groupe.
Mise à jour de la navigation
Dans le composant “navigation.component.ts”, ajoutez la fonction booléenne ci-dessous pour savoir si l’utilisateur dispose du rôle “view-managers”.
get isManager(): boolean {
return this.keycloak.hasRealmRole('view-managers') ?? false;
}
Afin de conditionner l’affichage dans la partie html (“navigation.component.html”).
@if(isManager) {
<li>
<a
routerLink="/managers"
routerLinkActive="active"
ariaCurrentWhenActive="page"
>Managers</a>
</li>
}
Nouveau guard et nouvelle route
Générez le nouveau guard “managers.guard.ts” avec la commande ci-dessous.
$ npx ng generate guard managers --skip-tests
✔ Which type of guard would you like to create? CanActivate
CREATE src/app/managers.guard.ts (132 bytes)
On vérifie que l’utilisateur connecté possède le rôle “view-managers” sinon, on le redirige sur la page du dashboard.
import { inject, Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import Keycloak from 'keycloak-js';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ManagersGuard implements CanActivate {
private readonly keycloak = inject(Keycloak);
private readonly router = inject(Router);
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
if (this.keycloak.hasRealmRole('view-managers')) {
return true;
}
this.router.navigate(['/dashboard']);
return false;
}
}
Avant de créer la route, générez le composant “managers”.
$ npx ng generate component managers --skip-tests --inline-template --style none
CREATE src/app/managers/managers.component.ts (196 bytes)
Afin de modifier le fichier des routes (“app.routes.ts”).
{
path: 'managers',
component: ManagersComponent,
canActivate: [KeycloakGuard, ManagersGuard],
},
Résultat
Avec un utilisateur disposant du rôle, on accède bien à la page “managers”.
Avec un utilisateur ne disposant pas du rôle, on n’a pas accès à la page.
Conclusion
La librairie “keycloak-angular” couplée à “keycloak-js” permet d’obtenir une connexion à Keycloak rapidement. La gestion des rôles permet de mettre en place un gestion plus fine de vos utilisateurs.
A noter que la mire d’authentification de Keycloak, est personnalisable afin de mieux coller au thème de votre application web.
Sources
- Keycloak Angular : GitHub – mauriciovigolo/keycloak-angular: Easy Keycloak integration for Angular applications. ;
- PicoCSS : https://picocss.com.
À lire aussi

Formations Atlassian certifiées Qualiopi avec Ouidou

Keycloak : Installation & configuration

Le Diamond Agile : Une approche évolutive pour une agilité maximale
