From f0faa63ca7589aa13bd0fd7c4c8571275f42090a Mon Sep 17 00:00:00 2001 From: akastijn Date: Fri, 4 Jul 2025 22:31:41 +0200 Subject: [PATCH] Add JWT support for authentication handling Integrate `@auth0/angular-jwt` for Token management. Update `app.config.ts` with `JwtModule` setup and token getter from cookies. Enhance `AuthService` to include token handling, fake login, and JWT validation using `JwtHelperService`. Introduce `JwtClaims` interface for structured token claims. --- frontend/package.json | 1 + frontend/src/app/app.config.ts | 20 +++++++++++- frontend/src/app/services/auth.service.ts | 32 +++++++++++-------- .../login/login.component.ts | 3 +- frontend/src/app/types/jwt_interface.ts | 7 ++++ 5 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 frontend/src/app/types/jwt_interface.ts diff --git a/frontend/package.json b/frontend/package.json index a978a67..9ca3a38 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "@angular/platform-browser": "^19.2.0", "@angular/platform-browser-dynamic": "^19.2.0", "@angular/router": "^19.2.0", + "@auth0/angular-jwt": "^5.2.0", "@types/three": "^0.177.0", "ngx-cookie-service": "^19.1.2", "rxjs": "~7.8.0", diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 0460e6a..153aa27 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -1,8 +1,26 @@ import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core'; import {provideRouter} from '@angular/router'; +import {JwtHelperService, JwtModule} from '@auth0/angular-jwt'; +import {CookieService} from 'ngx-cookie-service'; import {routes} from './app.routes'; +// Function to get the JWT token from cookies +export function jwtTokenGetter() { + const cookieService = new CookieService(document, null); + return cookieService.check('jwt') ? cookieService.get('jwt') : null; +} + export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({eventCoalescing: true}), provideRouter(routes)] + providers: [ + provideZoneChangeDetection({eventCoalescing: true}), + provideRouter(routes), + { provide: CookieService, useClass: CookieService }, + { provide: JwtHelperService, useClass: JwtHelperService }, + { provide: JwtModule, useValue: JwtModule.forRoot({ + config: { + tokenGetter: jwtTokenGetter + } + })} + ] }; diff --git a/frontend/src/app/services/auth.service.ts b/frontend/src/app/services/auth.service.ts index d7af5d6..5de77d6 100644 --- a/frontend/src/app/services/auth.service.ts +++ b/frontend/src/app/services/auth.service.ts @@ -4,6 +4,7 @@ import {CookieService} from 'ngx-cookie-service'; import {BehaviorSubject, Observable, throwError} from 'rxjs'; import {catchError, tap} from 'rxjs/operators'; import {MatSnackBar} from '@angular/material/snack-bar'; +import {JwtHelperService} from '@auth0/angular-jwt'; @Injectable({ providedIn: 'root' @@ -12,13 +13,14 @@ export class AuthService { private isAuthenticatedSubject = new BehaviorSubject(false); public isAuthenticated$ = this.isAuthenticatedSubject.asObservable(); - private userClaimsSubject = new BehaviorSubject(null); + private userClaimsSubject = new BehaviorSubject(null); public userClaims$ = this.userClaimsSubject.asObservable(); constructor( private loginService: LoginService, private cookieService: CookieService, - private snackBar: MatSnackBar + private snackBar: MatSnackBar, + private jwtHelper: JwtHelperService ) { // Check if user is already logged in on service initialization this.checkAuthStatus(); @@ -30,7 +32,7 @@ export class AuthService { public login(code: string): Observable { return this.loginService.login(code).pipe( tap(jwt => { - this.saveJwt(jwt as JsonWebKey); + this.saveJwt(jwt); this.isAuthenticatedSubject.next(true); }), catchError(error => { @@ -40,6 +42,13 @@ export class AuthService { ); } + public fakeLogin(): Observable { + const jwt = 'ZXlKaGJHY2lPaUpTVXpJMU5pSjkuZXlKcGMzTWlPaUpoYkhScGRIVmtaWGRsWWlJc0luTjFZaUk2SWpVMVpUUTJZbU16TFRKaE1qa3ROR00xTXkwNE5UQm1MV1JpWkRrME5HUmpOV00xWmlJc0ltVjRjQ0k2TVRjMU5ESTFNREUyTkN3aWFXRjBJam94TnpVeE5qVTRNVFkwTENKaGRYUm9iM0pwZEdsbGN5STZXeUpUUTA5UVJWOTFjMlZ5SWwxOS5iWU5Ba0hjUEp5ektReTl0cjN2RFB2VUNyX0FtQTNNVEtrZmlLV0dzV2M1QTdjR3ZKay1UYmQzdEJzYU1LZjI0ZUotZmtudjEyWFBGaXRXSmdRRVdNYTJvc19EYWkwZm1kT1J3MUlRb2d5bndOamtnWjBnMUJfU1lCMVRpZV9YQXpJTkNJSkNXMHB4YTB5U0dQUWxPaTJjVE1uWjdlRDlHMkI5NW0tcklMNG9NVDJMa3NwSjR2NXNCSlVrclNEaEl2dkQxOUhvWlFNV0VpU3BUbkQxVVhCSC1NVFVBMWhSSUFaZEd1cTVfWmpsQWNpMlpya195c0ZreGZBYVZHRW4zQXlpMFNmMjZhbEFlSGVxaW90M1lvWDR5djYyd0treThaODdkZVdMcWxvUHBzdi1INXNVX1E3dzhMano4cS1Ecl9hMHVRMlF2bDc4Y1RDV1JtTm04dlE='; + this.saveJwt(jwt); + this.isAuthenticatedSubject.next(true); + return undefined as any; + } + /** * Log the user out by removing the JWT */ @@ -56,14 +65,13 @@ export class AuthService { const jwt = this.getJwt(); if (jwt) { try { - const claims = this.extractJwtClaims(jwt as JsonWebKey); // Check if token is expired - const currentTime = Math.floor(Date.now() / 1000); - if (claims.exp && claims.exp < currentTime) { + if (this.jwtHelper.isTokenExpired(jwt)) { this.logout(); return false; } + const claims = this.extractJwtClaims(jwt); this.userClaimsSubject.next(claims); this.isAuthenticatedSubject.next(true); return true; @@ -84,8 +92,8 @@ export class AuthService { /** * Save the JWT to cookies */ - private saveJwt(jwt: JsonWebKey): void { - this.cookieService.set('jwt', jwt.toString(), { + private saveJwt(jwt: string): void { + this.cookieService.set('jwt', jwt, { path: '/', secure: true, sameSite: 'Strict' @@ -93,16 +101,14 @@ export class AuthService { const claims = this.extractJwtClaims(jwt); this.userClaimsSubject.next(claims); + } /** * Extract claims from JWT */ - private extractJwtClaims(jwt: JsonWebKey): any { - const token = jwt.toString(); - const base64Url = token.split('.')[1]; - const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - return JSON.parse(window.atob(base64)); + private extractJwtClaims(jwt: string): JwtClaims { + return this.jwtHelper.decodeToken(jwt); } /** diff --git a/frontend/src/app/shared-components/login/login.component.ts b/frontend/src/app/shared-components/login/login.component.ts index 76e63f1..0b0c6ba 100644 --- a/frontend/src/app/shared-components/login/login.component.ts +++ b/frontend/src/app/shared-components/login/login.component.ts @@ -53,7 +53,8 @@ export class LoginDialogComponent { return; } this.snackBar.open('Logging in...', '', {duration: 2000}); - this.authService.login(this.loginForm.value.code).subscribe({ + // this.authService.login(this.loginForm.value.code).subscribe({ + this.authService.fakeLogin().subscribe({ next: (jwt) => { this.dialogRef.close(jwt); }, diff --git a/frontend/src/app/types/jwt_interface.ts b/frontend/src/app/types/jwt_interface.ts new file mode 100644 index 0000000..f306f9b --- /dev/null +++ b/frontend/src/app/types/jwt_interface.ts @@ -0,0 +1,7 @@ +interface JwtClaims { + iss?: string; + sub?: string; + iat?: number; + exp?: number; + authorizations?: string[]; +}