added intercepter + auto login
This commit is contained in:
parent
b14a4a38a0
commit
e308a7bace
|
@ -1,11 +1,15 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
import { AuthService } from './shared/service';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
providers: [],
|
providers: [AuthService],
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss',
|
||||||
})
|
})
|
||||||
export class AppComponent {}
|
export class AppComponent {
|
||||||
|
private readonly authService: AuthService = inject(AuthService);
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||||
import { ApplicationConfig } from '@angular/core';
|
import { ApplicationConfig } from '@angular/core';
|
||||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
import { provideRouter, withComponentInputBinding } from '@angular/router';
|
import { provideRouter, withComponentInputBinding } from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
import { AuthInterceptor } from './shared/interceptors/auth.interceptor';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
|
provideHttpClient(withInterceptors([AuthInterceptor])),
|
||||||
provideRouter(routes, withComponentInputBinding()),
|
provideRouter(routes, withComponentInputBinding()),
|
||||||
provideAnimations(),
|
provideAnimations(),
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import {
|
||||||
|
HttpErrorResponse,
|
||||||
|
HttpEvent,
|
||||||
|
HttpHandlerFn,
|
||||||
|
HttpInterceptorFn,
|
||||||
|
HttpRequest,
|
||||||
|
} from '@angular/common/http';
|
||||||
|
import { inject } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable, catchError, switchMap, throwError } from 'rxjs';
|
||||||
|
|
||||||
|
import { AuthService } from '../service';
|
||||||
|
import { Tokens } from '../types';
|
||||||
|
|
||||||
|
export const AuthInterceptor: HttpInterceptorFn = (
|
||||||
|
request: HttpRequest<unknown>,
|
||||||
|
next: HttpHandlerFn
|
||||||
|
): Observable<HttpEvent<unknown>> => {
|
||||||
|
const authService: AuthService = inject(AuthService);
|
||||||
|
const accessToken: string | null = authService.access_token;
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
request = request.clone({
|
||||||
|
setHeaders: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return next(request).pipe(
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
if (error.status === 401) {
|
||||||
|
return authService.refreshToken().pipe(
|
||||||
|
switchMap((tokens: Tokens) => {
|
||||||
|
request = request.clone({
|
||||||
|
setHeaders: {
|
||||||
|
Authorization: `Bearer ${tokens.access_token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return next(request);
|
||||||
|
}),
|
||||||
|
catchError((refreshError) => {
|
||||||
|
authService.signout();
|
||||||
|
return throwError(() => new Error(refreshError));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return throwError(() => new Error());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, Observable, tap } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { LoginCredentials, Tokens } from '../types';
|
import { LoginCredentials, Tokens } from '../types';
|
||||||
|
@ -13,21 +13,27 @@ import { SessionStorageService } from './session-storage.service';
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private readonly path: string = '/api/auth';
|
private readonly _path: string = '/api/auth';
|
||||||
private access_token: string | null = null;
|
private _access_token: string | null = null;
|
||||||
private refresh_token: string | null = null;
|
private _refresh_token: string | null = null;
|
||||||
private isAuthenticated$: BehaviorSubject<boolean> =
|
private _isAuthenticated$: BehaviorSubject<boolean> =
|
||||||
new BehaviorSubject<boolean>(false);
|
new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
public get access_token(): string | null {
|
||||||
|
return this._access_token;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly httpClient: HttpClient,
|
private readonly httpClient: HttpClient,
|
||||||
private readonly localStorageService: LocalStorageService,
|
private readonly localStorageService: LocalStorageService,
|
||||||
private readonly sessionStorageService: SessionStorageService
|
private readonly sessionStorageService: SessionStorageService
|
||||||
) {}
|
) {
|
||||||
|
this.autoLogin();
|
||||||
|
}
|
||||||
|
|
||||||
public signin(credentials: LoginCredentials): void {
|
public signin(credentials: LoginCredentials): void {
|
||||||
this.httpClient
|
this.httpClient
|
||||||
.post<Tokens>(environment.api.base + `${this.path}/signin`, credentials)
|
.post<Tokens>(environment.api.base + `${this._path}/signin`, credentials)
|
||||||
.subscribe((response: Tokens) => {
|
.subscribe((response: Tokens) => {
|
||||||
this.handleSuccess(response);
|
this.handleSuccess(response);
|
||||||
});
|
});
|
||||||
|
@ -35,43 +41,60 @@ export class AuthService {
|
||||||
|
|
||||||
public signup(credentials: LoginCredentials): void {
|
public signup(credentials: LoginCredentials): void {
|
||||||
this.httpClient
|
this.httpClient
|
||||||
.post<Tokens>(environment.api.base + `${this.path}/signup`, credentials)
|
.post<Tokens>(environment.api.base + `${this._path}/signup`, credentials)
|
||||||
.subscribe((response: Tokens) => {
|
.subscribe((response: Tokens) => {
|
||||||
// The checked accept terms should be saved with a timestamp in the db
|
//TODO The checked accept terms should be saved with a timestamp in the db
|
||||||
this.handleSuccess(response);
|
this.handleSuccess(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public signout(): void {
|
public signout(): void {
|
||||||
this.access_token = null;
|
this._access_token = null;
|
||||||
this.refresh_token = null;
|
this._refresh_token = null;
|
||||||
this.localStorageService.removeItem('access_token');
|
this.localStorageService.removeItem('access_token');
|
||||||
this.sessionStorageService.removeItem('refresh_token');
|
this.sessionStorageService.removeItem('refresh_token');
|
||||||
this.isAuthenticated$.next(false);
|
this._isAuthenticated$.next(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshToken(): void {
|
public autoLogin(): void {
|
||||||
|
const storedAccessToken: string | null =
|
||||||
|
this.localStorageService.getItem('access_token');
|
||||||
|
const storedRefreshToken: string | null =
|
||||||
|
this.sessionStorageService.getItem('refresh_token');
|
||||||
|
|
||||||
|
if (storedAccessToken && storedRefreshToken) {
|
||||||
|
this._refresh_token = storedRefreshToken;
|
||||||
|
this._isAuthenticated$.next(true);
|
||||||
|
//TODO Validate tokens with backend or decode JWT to check expiration
|
||||||
|
} else {
|
||||||
|
this.signout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public refreshToken(): Observable<Tokens> {
|
||||||
const headers = new HttpHeaders().set(
|
const headers = new HttpHeaders().set(
|
||||||
'Authorization',
|
'Authorization',
|
||||||
'Bearer ' + this.refresh_token
|
'Bearer ' + this._refresh_token
|
||||||
);
|
);
|
||||||
|
|
||||||
this.httpClient
|
return this.httpClient
|
||||||
.post<Tokens>(
|
.post<Tokens>(
|
||||||
environment.api.base + `${this.path}/refresh`,
|
environment.api.base + `${this._path}/refresh`,
|
||||||
{},
|
{},
|
||||||
{ headers: headers }
|
{ headers: headers }
|
||||||
)
|
)
|
||||||
.subscribe((response: Tokens) => {
|
.pipe(
|
||||||
|
tap((response: Tokens) => {
|
||||||
this.handleSuccess(response);
|
this.handleSuccess(response);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleSuccess(tokens: Tokens): void {
|
private handleSuccess(tokens: Tokens): void {
|
||||||
this.access_token = tokens.access_token;
|
this._access_token = tokens.access_token;
|
||||||
this.refresh_token = tokens.refresh_token;
|
this._refresh_token = tokens.refresh_token;
|
||||||
this.localStorageService.setItem('access_token', tokens.access_token);
|
this.localStorageService.setItem('access_token', tokens.access_token);
|
||||||
this.sessionStorageService.setItem('refresh_token', tokens.refresh_token);
|
this.sessionStorageService.setItem('refresh_token', tokens.refresh_token);
|
||||||
this.isAuthenticated$.next(true);
|
this._isAuthenticated$.next(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue