diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 27fc937..ff0d3db 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,7 +1,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { DragDropModule } from '@angular/cdk/drag-drop'; @@ -39,6 +39,7 @@ import { TableOverviewComponent } from './pages/table-overview/table-overview.co import { CellComponent } from './components/tableComponents/cell/cell.component'; import { MenuListItemComponent } from './components/menu-list-item/menu-list-item.component'; import { NavService }from './components/menu-list-item/nav.service'; +import { TokenInterceptor } from './helper/token.interceptor' @NgModule({ declarations: [ @@ -80,7 +81,9 @@ import { NavService }from './components/menu-list-item/nav.service'; MatPaginatorModule, MatSortModule ], - providers: [NavService], + providers: [NavService, + { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }], + bootstrap: [AppComponent], }) export class AppModule { diff --git a/src/app/helper/token.interceptor.ts b/src/app/helper/token.interceptor.ts new file mode 100644 index 0000000..693ca02 --- /dev/null +++ b/src/app/helper/token.interceptor.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http'; +import { AuthService } from '../services/auth.service'; +import { Observable, throwError, BehaviorSubject } from 'rxjs'; +import { catchError, filter, take, switchMap } from 'rxjs/operators'; + +@Injectable() +export class TokenInterceptor implements HttpInterceptor { + + private isRefreshing = false; + private refreshTokenSubject: BehaviorSubject = new BehaviorSubject(null); + + constructor(public authService: AuthService) { } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + + if (this.authService.getRequestToken()) { + request = this.addToken(request, this.authService.getRequestToken()); + } + + return next.handle(request).pipe(catchError(error => { + if (error instanceof HttpErrorResponse && error.status === 401) { + console.log("catching error"); + return this.handle401Error(request, next); + } else { + return throwError(error); + } + })); + } + + private addToken(request: HttpRequest, token: string) { + return request.clone({ + setHeaders: { + 'Authorization': `Bearer ${token}` + } + }); + } + + private handle401Error(request: HttpRequest, next: HttpHandler) { + if (!this.isRefreshing) { + this.isRefreshing = true; + this.refreshTokenSubject.next(null); + + return this.authService.refreshToken().pipe( + switchMap((token: any) => { + this.isRefreshing = false; + this.refreshTokenSubject.next(token.jwt); + return next.handle(this.addToken(request, token.jwt)); + })); + + } else { + return this.refreshTokenSubject.pipe( + filter(token => token != null), + take(1), + switchMap(jwt => { + return next.handle(this.addToken(request, jwt)); + })); + } + } +} \ No newline at end of file diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index c20e10e..292de6b 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -9,6 +9,8 @@ import { BehaviorSubject } from 'rxjs'; }) export class AuthService { public loggedIn: BehaviorSubject; + private readonly REQUEST_TOKEN = 'requestToken'; + private readonly REFRESH_TOKEN = 'refreshToken'; constructor(private http: HttpClient) { this.loggedIn = new BehaviorSubject(false); @@ -16,15 +18,15 @@ export class AuthService { } private checkIfUserIsLoggedIn(): void { - this.loggedIn.next(!!this.requestToken); + this.loggedIn.next(!!this.getRequestToken()); } - public get requestToken(): string { - return localStorage.getItem('requestToken'); + public getRequestToken(): string { + return localStorage.getItem(this.REQUEST_TOKEN); } - public get refreshToken(): string { - return localStorage.getItem('refreshToken'); + public getRefreshToken(): string { + return localStorage.getItem(this.REFRESH_TOKEN); } login(email: string, password: string) { @@ -33,8 +35,7 @@ export class AuthService { .pipe( map((response) => { // store request and refresh token in local storage to keep user logged in between page refreshes - localStorage.setItem('requestToken', response.request_token); - localStorage.setItem('refreshToken', response.refresh_token); + this.storeTokens(response); this.checkIfUserIsLoggedIn(); }) ); @@ -43,11 +44,34 @@ export class AuthService { logout() { // remove token from local storage to log user out return this.http - .post(`${environment.authUrl}/logout`, { request_token: this.requestToken }).pipe(finalize(() => { - localStorage.removeItem('requestToken'); - localStorage.removeItem('refreshToken'); + .post(`${environment.authUrl}/logout`, { request_token: this.getRequestToken() }).pipe(finalize(() => { + this.removeTokens(); this.checkIfUserIsLoggedIn(); } )); } + + refreshToken() { + return this.http.post(`${environment.authUrl}/new-token`, { + 'refresh_token': this.getRefreshToken() + }).pipe(tap((tokens: any) => { + this.storeTokens(tokens); + })); + } + + private storeRequestToken(jwt: string) { + localStorage.setItem(this.REQUEST_TOKEN, jwt); + } + + private storeTokens(tokens: any) { + localStorage.setItem(this.REQUEST_TOKEN, tokens.request_token); + localStorage.setItem(this.REFRESH_TOKEN, tokens.refresh_token); + } + + private removeTokens() { + localStorage.removeItem(this.REQUEST_TOKEN); + localStorage.removeItem(this.REFRESH_TOKEN); + } + + }