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.
This commit is contained in:
akastijn 2025-07-04 22:31:41 +02:00
parent dfea91d8ca
commit f0faa63ca7
5 changed files with 48 additions and 15 deletions

View File

@ -22,6 +22,7 @@
"@angular/platform-browser": "^19.2.0", "@angular/platform-browser": "^19.2.0",
"@angular/platform-browser-dynamic": "^19.2.0", "@angular/platform-browser-dynamic": "^19.2.0",
"@angular/router": "^19.2.0", "@angular/router": "^19.2.0",
"@auth0/angular-jwt": "^5.2.0",
"@types/three": "^0.177.0", "@types/three": "^0.177.0",
"ngx-cookie-service": "^19.1.2", "ngx-cookie-service": "^19.1.2",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",

View File

@ -1,8 +1,26 @@
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core'; import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
import {provideRouter} from '@angular/router'; import {provideRouter} from '@angular/router';
import {JwtHelperService, JwtModule} from '@auth0/angular-jwt';
import {CookieService} from 'ngx-cookie-service';
import {routes} from './app.routes'; 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 = { 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
}
})}
]
}; };

View File

@ -4,6 +4,7 @@ import {CookieService} from 'ngx-cookie-service';
import {BehaviorSubject, Observable, throwError} from 'rxjs'; import {BehaviorSubject, Observable, throwError} from 'rxjs';
import {catchError, tap} from 'rxjs/operators'; import {catchError, tap} from 'rxjs/operators';
import {MatSnackBar} from '@angular/material/snack-bar'; import {MatSnackBar} from '@angular/material/snack-bar';
import {JwtHelperService} from '@auth0/angular-jwt';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -12,13 +13,14 @@ export class AuthService {
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false); private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable(); public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
private userClaimsSubject = new BehaviorSubject<any>(null); private userClaimsSubject = new BehaviorSubject<JwtClaims | null>(null);
public userClaims$ = this.userClaimsSubject.asObservable(); public userClaims$ = this.userClaimsSubject.asObservable();
constructor( constructor(
private loginService: LoginService, private loginService: LoginService,
private cookieService: CookieService, private cookieService: CookieService,
private snackBar: MatSnackBar private snackBar: MatSnackBar,
private jwtHelper: JwtHelperService
) { ) {
// Check if user is already logged in on service initialization // Check if user is already logged in on service initialization
this.checkAuthStatus(); this.checkAuthStatus();
@ -30,7 +32,7 @@ export class AuthService {
public login(code: string): Observable<any> { public login(code: string): Observable<any> {
return this.loginService.login(code).pipe( return this.loginService.login(code).pipe(
tap(jwt => { tap(jwt => {
this.saveJwt(jwt as JsonWebKey); this.saveJwt(jwt);
this.isAuthenticatedSubject.next(true); this.isAuthenticatedSubject.next(true);
}), }),
catchError(error => { catchError(error => {
@ -40,6 +42,13 @@ export class AuthService {
); );
} }
public fakeLogin(): Observable<any> {
const jwt = 'ZXlKaGJHY2lPaUpTVXpJMU5pSjkuZXlKcGMzTWlPaUpoYkhScGRIVmtaWGRsWWlJc0luTjFZaUk2SWpVMVpUUTJZbU16TFRKaE1qa3ROR00xTXkwNE5UQm1MV1JpWkRrME5HUmpOV00xWmlJc0ltVjRjQ0k2TVRjMU5ESTFNREUyTkN3aWFXRjBJam94TnpVeE5qVTRNVFkwTENKaGRYUm9iM0pwZEdsbGN5STZXeUpUUTA5UVJWOTFjMlZ5SWwxOS5iWU5Ba0hjUEp5ektReTl0cjN2RFB2VUNyX0FtQTNNVEtrZmlLV0dzV2M1QTdjR3ZKay1UYmQzdEJzYU1LZjI0ZUotZmtudjEyWFBGaXRXSmdRRVdNYTJvc19EYWkwZm1kT1J3MUlRb2d5bndOamtnWjBnMUJfU1lCMVRpZV9YQXpJTkNJSkNXMHB4YTB5U0dQUWxPaTJjVE1uWjdlRDlHMkI5NW0tcklMNG9NVDJMa3NwSjR2NXNCSlVrclNEaEl2dkQxOUhvWlFNV0VpU3BUbkQxVVhCSC1NVFVBMWhSSUFaZEd1cTVfWmpsQWNpMlpya195c0ZreGZBYVZHRW4zQXlpMFNmMjZhbEFlSGVxaW90M1lvWDR5djYyd0treThaODdkZVdMcWxvUHBzdi1INXNVX1E3dzhMano4cS1Ecl9hMHVRMlF2bDc4Y1RDV1JtTm04dlE=';
this.saveJwt(jwt);
this.isAuthenticatedSubject.next(true);
return undefined as any;
}
/** /**
* Log the user out by removing the JWT * Log the user out by removing the JWT
*/ */
@ -56,14 +65,13 @@ export class AuthService {
const jwt = this.getJwt(); const jwt = this.getJwt();
if (jwt) { if (jwt) {
try { try {
const claims = this.extractJwtClaims(jwt as JsonWebKey);
// Check if token is expired // Check if token is expired
const currentTime = Math.floor(Date.now() / 1000); if (this.jwtHelper.isTokenExpired(jwt)) {
if (claims.exp && claims.exp < currentTime) {
this.logout(); this.logout();
return false; return false;
} }
const claims = this.extractJwtClaims(jwt);
this.userClaimsSubject.next(claims); this.userClaimsSubject.next(claims);
this.isAuthenticatedSubject.next(true); this.isAuthenticatedSubject.next(true);
return true; return true;
@ -84,8 +92,8 @@ export class AuthService {
/** /**
* Save the JWT to cookies * Save the JWT to cookies
*/ */
private saveJwt(jwt: JsonWebKey): void { private saveJwt(jwt: string): void {
this.cookieService.set('jwt', jwt.toString(), { this.cookieService.set('jwt', jwt, {
path: '/', path: '/',
secure: true, secure: true,
sameSite: 'Strict' sameSite: 'Strict'
@ -93,16 +101,14 @@ export class AuthService {
const claims = this.extractJwtClaims(jwt); const claims = this.extractJwtClaims(jwt);
this.userClaimsSubject.next(claims); this.userClaimsSubject.next(claims);
} }
/** /**
* Extract claims from JWT * Extract claims from JWT
*/ */
private extractJwtClaims(jwt: JsonWebKey): any { private extractJwtClaims(jwt: string): JwtClaims {
const token = jwt.toString(); return <JwtClaims>this.jwtHelper.decodeToken(jwt);
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
} }
/** /**

View File

@ -53,7 +53,8 @@ export class LoginDialogComponent {
return; return;
} }
this.snackBar.open('Logging in...', '', {duration: 2000}); 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) => { next: (jwt) => {
this.dialogRef.close(jwt); this.dialogRef.close(jwt);
}, },

View File

@ -0,0 +1,7 @@
interface JwtClaims {
iss?: string;
sub?: string;
iat?: number;
exp?: number;
authorizations?: string[];
}