From 2723c5994add7716f31e26c62d8681ad8c39b33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=A4ume?= Date: Wed, 18 Dec 2024 16:17:44 +0100 Subject: [PATCH] Authorization with Keycloak --- README.md | 5 +++++ getBearerToken.http | 2 +- package-lock.json | 23 ++++++++++++++++++++ package.json | 3 ++- src/app/app.component.ts | 4 ++-- src/app/app.config.ts | 31 ++++++++++++++++++++------- src/app/core/auth/UserData.d.ts | 6 ++++++ src/app/core/auth/auth.service.ts | 32 ++++++++++++++++++++++++++++ src/app/header/header.component.html | 25 ++++++++++++++++------ src/app/header/header.component.ts | 11 +++++++--- src/app/types/.gitkeep | 0 11 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 src/app/core/auth/UserData.d.ts create mode 100644 src/app/core/auth/auth.service.ts delete mode 100644 src/app/types/.gitkeep diff --git a/README.md b/README.md index 652a59a..c9d02fc 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,8 @@ 1. Docker Container Hochfahren 1. Angular App starten +## Zugangsdaten + +**Benutzername:** `user` +**Passwort:** `test` + diff --git a/getBearerToken.http b/getBearerToken.http index e0429d9..32a27fb 100644 --- a/getBearerToken.http +++ b/getBearerToken.http @@ -1,4 +1,4 @@ -POST http://authproxy.szut.dev +POST http://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded grant_type=password&client_id=employee-management-service&username=user&password=test diff --git a/package-lock.json b/package-lock.json index 6cd8453..61eabc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", + "angular-auth-oidc-client": "^19.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -5955,6 +5956,22 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-auth-oidc-client": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/angular-auth-oidc-client/-/angular-auth-oidc-client-19.0.0.tgz", + "integrity": "sha512-CloBjmHjG6CxbFFjYB1Ei+e172JUY1V3cK/v9pdbVuUz3OhiMC6CxBr331oB3Em2eWMmLi23jecl10lfiy9WUQ==", + "license": "MIT", + "dependencies": { + "rfc4648": "^1.5.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=15.0.0", + "@angular/core": ">=15.0.0", + "@angular/router": ">=15.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -13545,6 +13562,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfc4648": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", + "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==", + "license": "MIT" + }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", diff --git a/package.json b/package.json index 0fe8260..e62ee65 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", + "angular-auth-oidc-client": "^19.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -54,4 +55,4 @@ "volta": { "node": "22.12.0" } -} \ No newline at end of file +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9be37f5..4a96a94 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import {RouterOutlet} from '@angular/router'; import {HeaderComponent} from '@app/header/header.component'; @Component({ @@ -8,5 +8,5 @@ import {HeaderComponent} from '@app/header/header.component'; templateUrl: './app.component.html', styleUrl: './app.component.scss' }) -export class AppComponent{ +export class AppComponent { } diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 988d8fd..fd8e79d 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,17 +1,32 @@ -import { provideHttpClient } from '@angular/common/http'; -import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; -import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; -import { provideRouter } from '@angular/router'; -import { routes } from '@app/app.routes'; -import { OpenAPI } from '@core/ems/core/OpenAPI'; +import {provideHttpClient} from '@angular/common/http'; +import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core'; +import {provideAnimationsAsync} from '@angular/platform-browser/animations/async'; +import {provideRouter} from '@angular/router'; +import {routes} from '@app/app.routes'; +import {OpenAPI} from '@core/ems/core/OpenAPI'; +import {LogLevel, provideAuth} from 'angular-auth-oidc-client'; + OpenAPI.BASE = 'http://localhost:8080'; OpenAPI.TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzUFQ0dldiNno5MnlQWk1EWnBqT1U0RjFVN0lwNi1ELUlqQWVGczJPbGU0In0.eyJleHAiOjE3MzQ1MjA4MzgsImlhdCI6MTczNDUxNzIzOCwianRpIjoiODVkM2NlNDQtM2QwMS00MDFkLTlhY2YtZjFhZTMwNzQzMDRmIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5zenV0LmRldi9hdXRoL3JlYWxtcy9zenV0IiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjU1NDZjZDIxLTk4NTQtNDMyZi1hNDY3LTRkZTNlZWRmNTg4OSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImVtcGxveWVlLW1hbmFnZW1lbnQtc2VydmljZSIsInNlc3Npb25fc3RhdGUiOiJiOWI3OGQ0ZS1jM2E4LTRiOWQtODdmMy04N2FiNzhjYzQyYTMiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NDIwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsicHJvZHVjdF9vd25lciIsIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1zenV0IiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIifQ.I3CGELu7IXrh5kYapp-v54c-R8hNqZOtvpmR98NhoTCrOVUoA-V16ayhF42qQ3Pj2YymxENpQWHR1-BTFQOJmAhQKBzvKWgOHuiLCPr8NkMGWFQ520BcJdipbGnsM8tmXuLE9FWezHA7LEGVkwY2gTbQMLEPq8v8hrtIf76F2Dq34BIG3Nq_6QlG10rG_Heqta6kWWe2p4DJmebdpvAeW2qvTLD4x89oIhtYXSiRIiVS2uAur5XuJcjpbv8UXWx11zVX7KelZEXky92q_xPKerFMOGt6up5MkCijJBhVWewSWNldaix7_jKTXCp70Gtu4sgCIuoojKp9Nts942RZjw'; export const appConfig: ApplicationConfig = { providers: [ - provideZoneChangeDetection({ eventCoalescing: true }), + provideZoneChangeDetection({eventCoalescing: true}), + provideAnimationsAsync(), provideRouter(routes), provideHttpClient(), - provideAnimationsAsync(), provideAnimationsAsync() + provideAuth({ + config: { + authority: 'https://keycloak.szut.dev/auth/realms/szut', + redirectUrl: window.location.origin, + postLogoutRedirectUri: window.location.origin, + clientId: 'employee-management-service', + scope: 'openid profile email offline_access', + responseType: 'code', + silentRenew: true, + useRefreshToken: true, + logLevel: LogLevel.Error, + }, + }) ], }; diff --git a/src/app/core/auth/UserData.d.ts b/src/app/core/auth/UserData.d.ts new file mode 100644 index 0000000..08b8212 --- /dev/null +++ b/src/app/core/auth/UserData.d.ts @@ -0,0 +1,6 @@ +interface UserData { + username: string, + verified: boolean +} + +export default UserData; diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts new file mode 100644 index 0000000..5926034 --- /dev/null +++ b/src/app/core/auth/auth.service.ts @@ -0,0 +1,32 @@ +import {Injectable} from '@angular/core'; +import UserData from '@core/auth/UserData'; +import {OidcSecurityService} from 'angular-auth-oidc-client'; +import {Observable} from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + public $user: Observable; + + constructor(private readonly oidcSecurityService: OidcSecurityService) { + this.$user = new Observable((publish) => { + this.oidcSecurityService.checkAuth().subscribe(({isAuthenticated, userData}) => { + publish.next(isAuthenticated ? { + username: userData.preferred_username, + verified: userData.email_verified + } : undefined); + }); + }); + + } + + login() { + this.oidcSecurityService.authorize(); + } + + logout() { + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); + } + +} diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 9f4a656..17702f3 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -1,12 +1,25 @@ - + + + @if (auth.$user|async; as user) { + + {{ user.username|titlecase }} + + + } @else { + + } \ No newline at end of file diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index 6296bd7..3e08782 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -1,9 +1,10 @@ -import {Location} from '@angular/common'; +import {AsyncPipe, Location, TitleCasePipe} from '@angular/common'; import {Component, OnInit} from '@angular/core'; -import {MatAnchor, MatButton} from '@angular/material/button'; +import {MatAnchor, MatButton, MatIconButton} from '@angular/material/button'; import {MatIcon} from '@angular/material/icon'; import {MatToolbar} from '@angular/material/toolbar'; import {Router, RouterLink} from '@angular/router'; +import {AuthService} from '@core/auth/auth.service'; @Component({ selector: 'app-header', @@ -13,6 +14,9 @@ import {Router, RouterLink} from '@angular/router'; RouterLink, MatIcon, MatButton, + MatIconButton, + AsyncPipe, + TitleCasePipe, ], templateUrl: './header.component.html', styleUrl: './header.component.scss' @@ -22,7 +26,8 @@ export class HeaderComponent implements OnInit { constructor( private router: Router, - private location: Location + private location: Location, + protected auth: AuthService ) { } diff --git a/src/app/types/.gitkeep b/src/app/types/.gitkeep deleted file mode 100644 index e69de29..0000000