1. Einführung
Das Ziel dieses Tutorials ist es, mithilfe von Keycloak und Angular eine einfache Web-Anwendung zu erstellen, welche eine Authentifizierung und Autorisierung mit Keycloak implementiert und somit Zugriffsrechte auf bestimmte Bereiche der Anwendung definiert.
1.1. Voraussetzungen
Folgendes wird benötigt:
-
Entwicklungsumgebung (WebStorm, Visual Studio Code, …)
-
Angular CLI
-
Node.js
-
Keycloak
Das folgende Tutorial baut auf dem Tutorial Securing Quarkus Backends with Keycloak auf. Daher wird das Durcharbeiten des Tutorials benötigt, um das folgende Tutorial absolvieren zu können. Wobei nur der PBAC-Teil (Policy-Based Access Control) benötigt wird. |
2. Setup
2.1. Keycloak installieren
2.1.1. Docker-Image pullen
docker pull quay.io/keycloak/keycloak:26.1.4
Für das Tutorial wird Keycloak in der Version 26.1.4 verwendet. Es ist empfohlen ebenfalls mit der Version 26.1.4 zu arbeiten, um mögliche Unterschiede in der Funktionalität bzw. Nutzung zu vermeiden. |
2.1.2. Docker-Image starten
docker run -d \ (1)
-p 8000:8080 \ (2)
-e KEYCLOAK_ADMIN=admin \ (3)
-e KEYCLOAK_ADMIN_PASSWORD=<PASSWORD> \ (4)
-e KC_PROXY=edge \ (5)
--restart always \ (6)
-v /<PATH>:/opt/jboss/keycloak/standalone/data \ (7)
quay.io/keycloak/keycloak:26.1.4 \ (8)
start-dev (9)
1 | Der -d-Flag (detached mode) sorgt dafür, dass der Container im Hintergrund läuft. |
2 | Bindet Port 8080 des Containers an Port 8000 des Hosts, sodass Keycloak über diesen Port erreichbar ist. (Port-Forwarding) |
3 | Legt den Benutzernamen des Keycloak-Administrators auf admin fest. |
4 | Legt das Passwort für den Keycloak-Administrator fest. (<PASSWORD> mit einem beliebigen Passwort ersetzen) |
5 | Legt den Proxy-Typ auf Edge fest. Keycloak läuft hierbei hinter einem Reverse Proxy. |
6 | Konfiguriert den Container so, dass er immer automatisch neu gestartet wird, falls der Container beispielsweise abstürzt. |
7 | Mountet ein Volume, um Keycloak-Daten zu speichern. Dadurch bleiben die Daten persistent, auch wenn der Container neu gestartet wird. |
8 | Gibt das Keycloak-Docker-Image mit Version 26.1.4 an. |
9 | Befehl zum Starten von Keycloak im Entwicklungsmodus. Dies bedeutet ebenfalls, dass einige Sicherheitsfeatures deaktiviert sind und das System schneller startet. |
3. Keycloak konfigurieren
Für die folgenden Schritte muss Keycloak gestartet sein. Siehe Setup. |
Falls bereits ein Realm, die passenden Rollen oder Benutzer im vorherigen Tutorial Securing Quarkus Backends with Keycloak erstellt worden sind, können die jeweiligen Schritte übersprungen werden.
Der Realm-Name des folgenden Tutorials weicht vom Realm-Name des vorherigen Tutorials ab, jedoch ist dies kein Problem. Es kann wie gehabt im bestehenden Realm weitergearbeitet werden, auch wenn der Realm-Name unterschiedlich ist, jedoch muss im Angular-Projekt beim Initialisieren von Keycloak der passende Realm-Name angegeben werden.
Was jedoch abgesehen vom Tutorial Securing Quarkus Backends with Keycloak nicht übersprungen werden kann, ist die Erstellung des Clients. Dieser muss für das Tutorial erstellt werden. |
3.1. Login
Zuerst muss muss man sich auf der Keycloak-Webseite anmelden. Hierfür muss man die URL http://localhost:8000 in den Browser eingeben.
Daraufhin sollte die Keycloak-Login-Seite erscheinen, welche wie folgt aussieht:

Nun muss man sich mit den Benutzernamen admin und dem zuvor festgelegten Passwort anmelden.
3.2. Realm
Bei erfolgreichem Login wird man auf folgende Seite weitergeleitet:

3.2.1. Was ist ein Realm?
Ein Realm in Keycloak ist eine isolierte Umgebung, in der Benutzer, Rollen und Clients verwaltet werden. Man kann sich einen Realm wie eine eigene Benutzerverwaltung für eine bestimmte Anwendung oder ein System vorstellen.
Ein Realm ist wie eine eigene "Firma" in Keycloak. Jede Firma hat ihre eigenen Mitarbeiter (Benutzer), Abteilungen (Rollen) und Anwendungen (Clients). |
Innerhalb eines Realms können sich Benutzer anmelden, Berechtigungen erhalten und Zugriff auf bestimmte Anwendungen bekommen.
Deutsche Synonyme für Realm sind Reich, Bereich und Königreich. |
3.2.2. Erstellen eines neuen Realms
Standardmäßig gibt es in Keycloak den Master-Realm. Dieser dient aber nur zur Verwaltung von Keycloak selbst. Daher muss für das Tutorial ein neuer Realm erstellt werden:

Der Realm-Name muss innerhalb der eigenen Keycloak-Instanz eindeutig sein und sollte für das Tutorial den Namen angular-policy-tutorial erhalten, um mögliche Fehler bzw. Unterschiede im weiteren Verlauf des Tutorials zu vermeiden. |

Falls man ein Realm temporär deaktivieren möchte, ist es besser den Schalter (Enabled) im Nachhinein umzulegen, anstatt den Realm komplett zu löschen. |
3.3. Client

3.3.1. Was ist ein Client?
Ein Client in Keycloak ist eine Anwendung oder ein Dienst, der die Authentifizierung und Autorisierung über Keycloak nutzt. Der Client kann eine Web-App, eine mobile App, eine API oder ein anderer Dienst sein, der auf Benutzeranmeldungen angewiesen ist.
3.3.2. Erstellen eines neuen Clients
Seite 1 (General settings)

Beschreibung der Felder (Seite 1)
1 | Der Client type in Keycloak bestimmt, welches Protokoll der Client für die Authentifizierung und Autorisierung verwendet. Hierbei gibt es zwei Optionen. Einerseits gibt es OpenID Connect, welches eine Erweiterung von OAuth 2.0 ist und für moderne Webanwendungen verwendet wird. Andererseits gibt es SAML (Security Assertion Markup Language), welches ein älteres Protokoll ist und für Single Sign-On (SSO) verwendet wird. Für das Tutorial ist OpenID Connect geeignet. |
2 | Client ID repräsentiert einen eindeutigen Namen des Clients, welcher innerhalb des Realms eindeutig sein muss. |
3 | Das Feld Name ist optional und dient zur Beschreibung des Clients. |
4 | Das Feld Description ist ebenfalls optional und dient zur detaillierteren Beschreibung des Clients, um beispielsweise den Zweck des Clients zu beschreiben. |
5 | Always display in UI ist ein Flag, welches bei einer Aktivierung dafür sorgt, dass der Client auf der Keycloak-Anmeldeseite als auswählbare Anwendung angezeigt wird. Bei einer Deaktivierung wird der Client nicht auf der Anmeldeseite angezeigt. Falls Benutzer sich direkt für eine bestimmte App anmelden sollen, kann es sinnvoll sein, den Client in der UI anzuzeigen. Für das Tutorial hingegen ist es nicht notwendig, den Client in der UI anzuzeigen. |
Mit dem Button Next wird die Eingabe bestätigt und man wird auf die zweite Seite weitergeleitet.
Seite 2 (Capability config)

Beschreibung der Felder (Seite 2)
1 | Wenn die Client authentication deaktiviert ist, benötigt der Client keine Authentifizierung, um auf Keycloak-Ressourcen zuzugreifen. Wenn das Feld deaktiviert ist, muss der Client sich mit einem geheimen Schlüssel (Client Secret) authentifizieren, bevor er eine Authentifizierungsanfrage stellen kann. Diese Option wird bei Servern bzw. Backend-Services verwendet. |
2 | Bei Deaktivierung der Funktion Authorization kann der Client frei auf geschützte Ressourcen zugreifen, ohne dass spezielle Berechtigungsprüfungen durch Keycloak erfolgen. Bei Aktivierung nutzt Keycloak sein Authorization Service-Feature, um Berechtigungen für diesen Client zu verwalten. |
3 | Mit dem Authentication flow wird festgelegt, welche OAuth 2.0-Flows der Client nutzen darf:
|
Mit dem Button Next wird die Eingabe bestätigt und man wird auf die letzte Seite weitergeleitet.
Seite 3 (Login settings)

Beschreibung der Felder (Seite 2)
1 | Die Root URL repräsentiert die Basis-URL der Anwendung, welche für diesen Client in Keycloak registriert wird. Da im weiteren Verlauf des Tutorials eine Angular-Anwendung erstellt wird, sollte die Root URL http://localhost:4200 sein, da es sich um eine lokale Angular-Anwendung handelt. |
2 | Die Home URL stellt jene URL da, auf die der Benutzer nach erfolgreicher Authentifizierung weitergeleitet wird. In diesem Tutorial wird der Benutzer nach einer erfolgreichen Authentifizierung auf die Home-Page (http://localhost:4200) weitergeleitet. |
3 | Die Valid redirect URIs bilden weitere zulässige URLs ab, auf die der Benutzer nach der Authentifizierung weitergeleitet werden darf. Mit http://localhost:4200/ wird das Weiterleiten zu sämtlichen Unterseiten der Angular-Anwendung erlaubt. |
4 | Die Valid post logout redirect URIs stellen URLs dar, auf die der Benutzer nach dem Logout weitergeleitet werden darf. Mit http://localhost:4200/ wird das Weiterleiten zu sämtlichen Unterseiten der Angular-Anwendung erlaubt. |
5 | Die Web Origins stellen die URLs fest, von denen aus die Anfragen an Keycloak gesendet werden können. Hierbei sollte die nur die tatsächlich URL des Clients eingetragen werden. In diesem Fall ist es http://localhost:4200. |
Mit dem Button Save wird die Eingabe bestätigt und der Client wird erstellt.
Nach dem man auf den Button Save geklickt hat, wird man auf eine Überblicksseite weitergeleitet, bei der man noch einmal alle Konfigurationen überprüfen und final bestätigen kann. |
3.4. Roles

3.4.1. Was ist eine Role?
Roles (Rollen) in Keycloak sind eine Möglichkeit, Benutzer zu gruppieren und ihre Berechtigungen zu definieren. Sie bestimmen, welche Aktionen ein Benutzer ausführen darf.
3.5. User

3.5.1. Erstellen mehrerer neuer User
Es ist möglich User manuell zu erstellen, aber auch aus externen Quellen zu importieren. In diesem Tutorial werden die User manuell erstellt. |
User 1
Zuerst wird ein User namens John Doe wie folgt erstellt:

Nun werden die Credentials bzw. das Passwort für den User John Doe gesetzt.

Nun wird das Passwort (john123) für den User John Doe gesetzt.

Das Flag Temporary wird für das Tutorial deaktiviert, da sonst beim nächsten Login das Passwort geändert werden muss. |
Zuletzt muss dem User John Doe noch eine Rolle zugewiesen werden.

Es werden die Rollen admin und user zugewiesen.

4. Backend
Das folgende Tutorial baut auf dem Tutorial Securing Quarkus Backends with Keycloak auf. Daher wird das Durcharbeiten des Tutorials benötigt, um das folgende Tutorial absolvieren zu können. Wobei nur der PBAC-Teil (Policy-Based Access Control) benötigt wird. |
4.1. application.properties anpassen
Im Backend (mit PBAC), welches im Tutorial Securing Quarkus Backends with Keycloak erstellt wurde, muss folgende Konfiguration hinzugefügt werden:
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = app
quarkus.datasource.password = app
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/db
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.physical-naming-strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
quarkus.oidc.auth-server-url=http://localhost:8000/realms/angular-policy-tutorial
quarkus.oidc.client-id=backend
quarkus.oidc.credentials.secret=<CLIENT_SECRET>
quarkus.keycloak.policy-enforcer.enable=true
quarkus.devservices.enabled=false
#### KOMMT NEU DAZU ####
quarkus.http.cors.enabled=true (1)
quarkus.http.cors.origins=/.*/ (2)
1 | Aktiviert CORS-Unterstützung im Quarkus-HTTP-Server. Ohne diese Einstellung würden Cross-Origin-Anfragen vom Browser blockiert werden. |
2 | Legt fest, dass alle Domains erlaubt sind. Dies ist in der Entwicklungsumgebung sinnvoll. In einer Produktionsumgebung sollte dies jedoch restriktiver gesetzt werden, um Sicherheitsrisiken zu vermeiden. |
5. Angular
Für das Tutorial wird Angular in der Version 19.2.5 verwendet. Siehe Angular-Setup. |
5.1. Angular-Projekt erstellen
Mit der Angular CLI kann ein neues Angular-Projekt wie folgt erstellt werden:
ng new ng-keycloak-policy-demo
Bei der Erstellung des Projekts werden einige Fragen gestellt, die wie folgt beantwortet werden:
-
Stylesheet format: CSS
-
Server-side rendering and Static Site Generation: No
Nun wird das Projekt in einer beliebigen IDE, welche Angular unterstützt, geöffnet, z. B. WebStorm oder Visual Studio Code.
Das Projekt kann jederzeit, sofern man sich im Projektverzeichnis befindet, mit folgendem Befehl gestartet werden:
ng serve
Das Projekt ist somit unter der URL http://localhost:4200 erreichbar.
5.2. Library installieren
Um die Keycloak-Integration in Angular zu ermöglichen, werden die keycloak-angular- und die keycloak-js-Library benötigt. Diese können mit dem folgenden Befehl installiert werden:
npm install keycloak-angular keycloak-js
5.3. Keycloak initialisieren
Um Keycloak in Angular zu initialisieren, muss die bestehende TypeScript-Datei app.config.ts erweitert werden.
import { ApplicationConfig, APP_INITIALIZER, inject } from '@angular/core';
import { provideRouter } from '@angular/router';
import { HttpInterceptorFn, provideHttpClient, withInterceptors } from '@angular/common/http';
import { KeycloakService } from 'keycloak-angular';
import { routes } from './app.routes';
const keycloakInterceptor: HttpInterceptorFn = (req, next) => {
const keycloak = inject(KeycloakService); (1)
const instance = keycloak.getKeycloakInstance(); (2)
if (instance?.token) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${instance.token}` (3)
}
});
}
return next(req);
};
function initializeKeycloak() { (4)
const keycloak = inject(KeycloakService);
return () =>
keycloak.init({
config: {
url: 'http://localhost:8000', (5)
realm: 'angular-policy-tutorial', (6)
clientId: 'angular-policy' (7)
},
initOptions: {
onLoad: 'login-required', (8)
checkLoginIframe: false (9)
}
});
}
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(
withInterceptors([keycloakInterceptor]) (10)
),
{
provide: APP_INITIALIZER, (11)
useFactory: initializeKeycloak,
multi: true
},
KeycloakService (12)
]
};
1 | Der KeycloakService wird mithilfe von inject() abgerufen, um innerhalb des Interceptors auf den aktuellen Authentifizierungsstatus zuzugreifen. |
2 | Über getKeycloakInstance() wird die native Keycloak-Instanz abgerufen, in der u. a. das Token gespeichert ist. |
3 | Falls ein Token vorhanden ist, wird es als Authorization: Bearer … Header an den HTTP-Request angehängt. |
4 | Die Funktion initializeKeycloak() konfiguriert und startet den Keycloak-Service beim App-Start. |
5 | Die URL des Keycloak-Servers. |
6 | Der Name des Realms, in dem sich der Client befindet. |
7 | Die Client-ID, mit der sich die Angular-App bei Keycloak authentifiziert. |
8 | onLoad: 'login-required' bedeutet, dass ein Benutzer automatisch zum Login weitergeleitet wird, wenn er nicht eingeloggt ist. |
9 | checkLoginIframe: false deaktiviert die regelmäßige Prüfung des Login-Status via verstecktem Iframe. Normalerweise prüft Keycloak regelmäßig in einem versteckten <iframe>, ob der Benutzer noch eingeloggt ist (Single-Sign-On-Session-Check). Der Vorteil bei der Deaktivierung ist, dass weniger HTTP-Traffic entsteht. |
10 | withInterceptors([keycloakInterceptor]) sorgt dafür, dass alle HTTP-Requests automatisch mit dem Access-Token versehen werden. |
11 | Mit APP_INITIALIZER wird initializeKeycloak() beim Start der Anwendung ausgeführt, noch bevor die App gerendert wird. |
12 | KeycloakService wird hier als Provider registriert, damit er überall in der App verwendet werden kann. |
Nun sollte man beim Starten der App auf die folgende Seite weitergeleitet werden:

Man kann sich nun entweder mit dem User John Doe oder mit dem User Jane Doe, welche beide zuvor im Tutorial erstellt wurden, anmelden.
Bei erfolgreichem Login sollte man auf die folgende Seite weitergeleitet werden:

5.4. Model erstellen
Um die Daten, welche vom Backend zurückgegeben werden, zu speichern, wird ein Model benötigt. Dieses Model sieht wie folgt aus:
export interface Vehicle{
id: number;
make: string;
model: string;
year: number;
}
5.5. Service erstellen
Um Daten aus dem Backend zu holen, wird ein Service benötigt. Dieses Service wird wie folgt erstellt:
ng generate service vehicle
Die generierte Service-Klasse sieht wie folgt aus:
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Vehicle } from '../model/vehicle';
@Injectable({
providedIn: 'root'
})
export class VehicleService {
private http = inject(HttpClient);
private backendUrl = 'http://localhost:8080/vehicle';
getAllVehicles() {
return this.http.get<Vehicle[]>(`${this.backendUrl}/`);
}
getVehicle(id: number) {
return this.http.get<Vehicle>(`${this.backendUrl}/${id}`);
}
}
Nach dem Erstellen des Models und des Services sollte die Projektstruktur wie folgt aussehen:
5.6. Komponente anpassen
Um die Daten aus dem Backend anzuzeigen, wird die bestehende Komponente app.component.ts angepasst.
import { Component, inject, OnInit } from '@angular/core';
import { VehicleService } from '../shared/services/vehicle.service';
import { Vehicle } from '../shared/model/vehicle';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { KeycloakService } from 'keycloak-angular';
@Component({
selector: 'app-root',
imports: [FormsModule, CommonModule, RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent implements OnInit{
title = 'ng-keycloak-policy-demo';
keycloakService = inject(KeycloakService);
vehicleService = inject(VehicleService);
allVehicles: Vehicle[] = [];
vehicleById: Vehicle | null = null;
vehicleId: number | null = null;
isAdmin: boolean = false;
isUser: boolean = false;
ngOnInit(){ (1)
this.isAdmin = this.keycloakService.getUserRoles().includes('admin');
this.isUser = this.keycloakService.getUserRoles().includes('user');
}
getAllVehicles(){
this.vehicleService.getAllVehicles().subscribe((vehicles) => { (2)
this.allVehicles = vehicles;
})
}
getVehicleById(){
if(this.vehicleId){
this.vehicleService.getVehicle(this.vehicleId).subscribe((vehicle) => { (3)
this.vehicleById = vehicle;
})
}
else{
console.log('The vehicle-id is not set!');
}
}
}
1 | Im ngOnInit() prüft die Komponente, ob der eingeloggte Benutzer die Rollen admin oder user hat. Damit kann z. B. im Template gesteuert werden, ob bestimmte Buttons sichtbar sind. |
2 | Die Methode getAllVehicles() ruft alle Fahrzeuge vom Backend ab und speichert sie in der allVehicles-Liste. Die Daten werden dann im Template angezeigt, jedoch nur, wenn der eingeloggte Benutzer die Rolle admin hat. |
3 | Die Methode getVehicleById() holt ein einzelnes Fahrzeug basierend auf der eingegebenen vehicleId und speichert es in vehicleById. Hierfür wird nur die Rolle user benötigt. |
Nun muss die HTML-Datei app.component.html angepasst werden, um die Daten anzuzeigen.
<div class="container">
<h1>{{title}}</h1>
<div class="button-group">
<div class="all-vehicles">
<button
[disabled]="!isAdmin" (1)
(click)="getAllVehicles()"
class="btn btn-primary">
Get all vehicles
</button>
<div class="vehicle-list">
@for (vehicle of allVehicles; track vehicle) {
<p>{{ vehicle.id }} {{ vehicle.make }} {{ vehicle.model }} {{ vehicle.year }}</p>
}
</div>
</div>
<div class="by-id">
@if (isUser) {
<select [(ngModel)]="vehicleId" class="id-input">
<option [ngValue]="1">1</option>
<option [ngValue]="2">2</option>
<option [ngValue]="3">3</option>
</select>
}
<button
(click)="getVehicleById()"
[disabled]="!vehicleId || !isUser" (2)
class="btn btn-secondary">
Get vehicle by id
</button>
<div class="vehicle-single">
@if (vehicleById) {
<p>{{ vehicleById.id }} {{ vehicleById.make }} {{ vehicleById.model }} {{ vehicleById.year }}</p>
}
</div>
</div>
</div>
</div>
<router-outlet></router-outlet>
1 | Der Button Get all vehicles ist nur sichtbar, wenn der eingeloggte Benutzer die Rolle admin hat. Andernfalls ist der Button deaktiviert. |
2 | Der Button Get vehicle by id ist nur sichtbar, wenn der eingeloggte Benutzer die Rolle user hat und eine vehicleId gesetzt ist. Andernfalls ist der Button deaktiviert. |
Zuletzt wird noch die CSS-Datei app.component.css angepasst, um die Daten etwas schöner darzustellen.
.container {
max-width: 1000px;
margin: 2rem auto;
padding: 2rem;
font-family: Arial, sans-serif;
background-color: #f9f9f9;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.container h1 {
text-align: center;
font-size: 2rem;
margin-bottom: 2rem;
color: #333;
}
.button-group {
display: flex;
gap: 2rem;
justify-content: space-between;
flex-wrap: wrap;
}
.all-vehicles {
flex: 1;
min-width: 280px;
}
.by-id {
flex: 1;
min-width: 280px;
}
.btn {
padding: 0.6rem 1.2rem;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
width: 100%;
margin-bottom: 1rem;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:disabled {
background-color: #b3d4fc;
cursor: not-allowed;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:disabled {
background-color: #d6d6d6;
cursor: not-allowed;
}
.id-input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1rem;
margin-bottom: 1rem;
}
.vehicle-list p, .vehicle-single p {
background-color: #ffffff;
padding: 0.5rem 1rem;
border-left: 4px solid #007bff;
border-radius: 6px;
margin-bottom: 0.5rem;
color: #444;
}