Compare commits

2 Commits

Author SHA1 Message Date
Florian THIERRY
882ffe7094 Several changes about token refreshing. 2024-10-15 16:11:46 +02:00
Florian THIERRY
136771ab60 Add i18n missing translation. 2024-10-15 09:35:14 +02:00
9 changed files with 72 additions and 23 deletions

View File

@@ -1,10 +1,11 @@
import { ApplicationConfig } from '@angular/core'; import { APP_INITIALIZER, ApplicationConfig } from '@angular/core';
import { provideRouter, withRouterConfig } from '@angular/router'; import { provideRouter, withRouterConfig } from '@angular/router';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { JwtInterceptor } from './core/interceptor/jwt.interceptor'; import { JwtInterceptor } from './core/interceptor/jwt.interceptor';
import { AuthenticationService } from './core/service/authentication.service';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@@ -18,5 +19,11 @@ export const appConfig: ApplicationConfig = {
provideAnimationsAsync(), provideAnimationsAsync(),
provideHttpClient(withInterceptorsFromDi()), provideHttpClient(withInterceptorsFromDi()),
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
{
provide: APP_INITIALIZER,
useFactory: (authenticationService: AuthenticationService) => () => authenticationService.startAuthenticationCheckingProcess(),
deps: [AuthenticationService],
multi: true
}
] ]
}; };

View File

@@ -3,11 +3,13 @@ import { CanActivateFn, Router } from "@angular/router";
import { AuthenticationService } from "../service/authentication.service"; import { AuthenticationService } from "../service/authentication.service";
import { MatSnackBar } from "@angular/material/snack-bar"; import { MatSnackBar } from "@angular/material/snack-bar";
export const authenticationGuard: CanActivateFn = () => { export const authenticationGuard: CanActivateFn = async () => {
const authenticationService = inject(AuthenticationService); const authenticationService = inject(AuthenticationService);
const router = inject(Router); const router = inject(Router);
const snackBar = inject(MatSnackBar); const snackBar = inject(MatSnackBar);
await authenticationService.checkIsAuthenticated();
if (authenticationService.isAuthenticated()) { if (authenticationService.isAuthenticated()) {
return true; return true;
} else { } else {

View File

@@ -41,23 +41,12 @@ export class JwtInterceptor implements HttpInterceptor {
this.isRefreshingToken = true; this.isRefreshingToken = true;
this.refreshTokenSubject.next(undefined); this.refreshTokenSubject.next(undefined);
const refreshToken = this.authenticationService.getRefreshToken(); this.authenticationService.refreshToken()
if (refreshToken) { .then(refreshTokenResponse => {
const refreshTokenRequest: RefreshTokenRequest = { this.refreshTokenSubject.next(refreshTokenResponse.accessToken);
refreshTokenValue: refreshToken })
}; .catch(() => this.handleNoRefreshToken(initialError))
this.userRestService.refreshToken(refreshTokenRequest) .finally(() => this.isRefreshingToken = false);
.then(refreshTokenResponse => {
this.authenticationService.authenticate(refreshTokenResponse.accessToken, refreshTokenResponse.refreshToken);
this.refreshTokenSubject.next(refreshTokenResponse.accessToken);
})
.catch(() => {
return this.handleNoRefreshToken(initialError);
})
.finally(() => this.isRefreshingToken = false);
} else {
return this.handleNoRefreshToken(initialError);
}
} }
return this.refreshTokenSubject.pipe( return this.refreshTokenSubject.pipe(

View File

@@ -1,7 +1,9 @@
import { inject, Injectable } from "@angular/core"; import { inject, Injectable } from "@angular/core";
import { BehaviorSubject, interval } from "rxjs";
import { User } from "../model/User"; import { User } from "../model/User";
import { UserRestService } from "../rest-services/user/user.rest-service"; import { UserRestService } from "../rest-services/user/user.rest-service";
import { RefreshTokenRequest } from "../rest-services/user/model/refresh-token.model"; import { RefreshTokenRequest } from "../rest-services/user/model/refresh-token.model";
import { LoginResponse } from "../rest-services/user/model/login.model";
const JWT_PARAM = 'jwt'; const JWT_PARAM = 'jwt';
const REFRESH_TOKEN_PARAM = 'refresh-token'; const REFRESH_TOKEN_PARAM = 'refresh-token';
@@ -18,18 +20,30 @@ interface UserDetails {
providedIn: 'root' providedIn: 'root'
}) })
export class AuthenticationService { export class AuthenticationService {
private readonly AUTHENTICATION_CHECKING_PERIOD = 5 * 60 * 1000;
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
private readonly userRestService = inject(UserRestService);
startAuthenticationCheckingProcess(): void {
this.checkIsAuthenticated();
interval(this.AUTHENTICATION_CHECKING_PERIOD)
.subscribe(() => this.checkIsAuthenticated());
}
authenticate(token: string, refreshToken: string): void { authenticate(token: string, refreshToken: string): void {
localStorage.setItem(JWT_PARAM, token); localStorage.setItem(JWT_PARAM, token);
localStorage.setItem(REFRESH_TOKEN_PARAM, refreshToken); localStorage.setItem(REFRESH_TOKEN_PARAM, refreshToken);
this.isAuthenticatedSubject.next(true);
} }
unauthenticate(): void { unauthenticate(): void {
localStorage.removeItem(JWT_PARAM); localStorage.removeItem(JWT_PARAM);
localStorage.removeItem(REFRESH_TOKEN_PARAM);
this.isAuthenticatedSubject.next(false);
} }
isAuthenticated(): boolean { isAuthenticated(): boolean {
return !!localStorage.getItem(JWT_PARAM); return this.isAuthenticatedSubject.value;
} }
getAuthenticatedUser(): User | undefined { getAuthenticatedUser(): User | undefined {
@@ -45,7 +59,7 @@ export class AuthenticationService {
} }
isTokenExpired(): boolean { isTokenExpired(): boolean {
let result = false; let result = true;
const userDetails = this.extractUserDetails(); const userDetails = this.extractUserDetails();
@@ -59,8 +73,43 @@ export class AuthenticationService {
return result; return result;
} }
refreshToken(): Promise<LoginResponse> {
const refreshToken = this.getRefreshToken();
if (refreshToken) {
const refreshTokenRequest: RefreshTokenRequest = {
refreshTokenValue: refreshToken
};
return this.userRestService.refreshToken(refreshTokenRequest)
.then(refreshTokenResponse => {
this.authenticate(refreshTokenResponse.accessToken, refreshTokenResponse.refreshToken);
return refreshTokenResponse;
});
}
return Promise.reject('No any refresh token found.');
}
checkIsAuthenticated(): Promise<void> {
return new Promise((resolve, reject) => {
const isTokenExpired = this.isTokenExpired();
if (isTokenExpired) {
if (this.getRefreshToken()) {
this.refreshToken()
.then(() => resolve())
.catch(() => reject());
} else {
this.isAuthenticatedSubject.next(false);
}
} else {
this.isAuthenticatedSubject.next(true);
}
resolve();
});
}
private extractUserFromLocalStorage(): User | undefined { private extractUserFromLocalStorage(): User | undefined {
let result: User | undefined = undefined; let result: User | undefined;
const userDetails = this.extractUserDetails(); const userDetails = this.extractUserDetails();
if (userDetails) { if (userDetails) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -46,6 +46,7 @@
"9122763438636464100": "Fermer le menu", "9122763438636464100": "Fermer le menu",
"1902100407096396858": "Catégories", "1902100407096396858": "Catégories",
"6940115735259407353": "Une erreur est survenue lors du chargement des catégories.", "6940115735259407353": "Une erreur est survenue lors du chargement des catégories.",
"3516191632292372377": "Vous ne pouvez pas accéder à cette page car vous êtes déjà connecté.",
"3450287383703155559": "Vous n'êtes pas connecté. Veuillez vous connecter avant de réessayer.", "3450287383703155559": "Vous n'êtes pas connecté. Veuillez vous connecter avant de réessayer.",
"5455465794443528807": "You n'êtes pas connecté. Veuillez vous connecter avant de réessayer votre opération.", "5455465794443528807": "You n'êtes pas connecté. Veuillez vous connecter avant de réessayer votre opération.",
"4011987306265136481": "Déconnexion...", "4011987306265136481": "Déconnexion...",

View File

@@ -1,5 +1,5 @@
{ {
"locale": "en-UK", "locale": "en",
"translations": { "translations": {
"3603720768157919481": " No ", "3603720768157919481": " No ",
"4861926948802653243": " Yes ", "4861926948802653243": " Yes ",
@@ -46,6 +46,7 @@
"9122763438636464100": "Close the menu", "9122763438636464100": "Close the menu",
"1902100407096396858": "Categories", "1902100407096396858": "Categories",
"6940115735259407353": "An error occured while loading categories.", "6940115735259407353": "An error occured while loading categories.",
"3516191632292372377": "You can't access to this page because you are already authenticated.",
"3450287383703155559": "You are unauthenticated. Please, log-in first.", "3450287383703155559": "You are unauthenticated. Please, log-in first.",
"5455465794443528807": "You are unauthenticated. Please, re-authenticate before retrying your action.", "5455465794443528807": "You are unauthenticated. Please, re-authenticate before retrying your action.",
"4011987306265136481": "Disconnection...", "4011987306265136481": "Disconnection...",