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:
parent
dfea91d8ca
commit
f0faa63ca7
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
7
frontend/src/app/types/jwt_interface.ts
Normal file
7
frontend/src/app/types/jwt_interface.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
interface JwtClaims {
|
||||||
|
iss?: string;
|
||||||
|
sub?: string;
|
||||||
|
iat?: number;
|
||||||
|
exp?: number;
|
||||||
|
authorizations?: string[];
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user