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-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",

View File

@ -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
}
})}
]
};

View File

@ -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<boolean>(false);
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
private userClaimsSubject = new BehaviorSubject<any>(null);
private userClaimsSubject = new BehaviorSubject<JwtClaims | null>(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<any> {
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<any> {
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 <JwtClaims>this.jwtHelper.decodeToken(jwt);
}
/**

View File

@ -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);
},

View File

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