-
-
Save Toilal/8849bd63d53bd2df2dd4df92d3b12f26 to your computer and use it in GitHub Desktop.
import { NgModule } from '@angular/core'; | |
import { CommonModule } from '@angular/common'; | |
import { JWT_OPTIONS, JwtInterceptor, JwtModule } from '@auth0/angular-jwt'; | |
import { AuthorizationService } from './authorization.service'; | |
import { environment } from '../../environments/environment'; | |
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; | |
import { RefreshTokenInterceptor } from './refresh-token-interceptor'; | |
function jwtOptionsFactory (authorizationService: AuthorizationService) { | |
return { | |
tokenGetter: () => { | |
return authorizationService.getAccessToken(); | |
}, | |
blacklistedRoutes: [`${environment.apiBaseUrl}/login-check`] | |
}; | |
} | |
@NgModule({ | |
imports: [ | |
CommonModule, | |
HttpClientModule, | |
JwtModule.forRoot({ | |
jwtOptionsProvider: { | |
provide: JWT_OPTIONS, | |
useFactory: jwtOptionsFactory, | |
deps: [AuthorizationService] | |
} | |
}) | |
], | |
providers: [ | |
AuthorizationService, | |
JwtInterceptor, // Providing JwtInterceptor allow to inject JwtInterceptor manually into RefreshTokenInterceptor | |
{ | |
provide: HTTP_INTERCEPTORS, | |
useExisting: JwtInterceptor, | |
multi: true | |
}, | |
{ | |
provide: HTTP_INTERCEPTORS, | |
useClass: RefreshTokenInterceptor, | |
multi: true | |
} | |
], | |
declarations: [] | |
}) | |
export class ApiModule { | |
} |
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; | |
import { Injectable } from '@angular/core'; | |
import { Observable, throwError } from 'rxjs'; | |
import { catchError, mergeMap } from 'rxjs/operators'; | |
import { AuthorizationService } from './authorization.service'; | |
import { JwtInterceptor } from '@auth0/angular-jwt'; | |
@Injectable() | |
export class RefreshTokenInterceptor implements HttpInterceptor { | |
constructor (private authorizationService: AuthorizationService, private jwtInterceptor: JwtInterceptor) { | |
} | |
intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
if (this.jwtInterceptor.isWhitelistedDomain(req) && !this.jwtInterceptor.isBlacklistedRoute(req)) { | |
return next.handle(req).pipe( | |
catchError((err) => { | |
const errorResponse = err as HttpErrorResponse; | |
if (errorResponse.status === 401 && errorResponse.error.message === 'Expired JWT Token') { | |
return this.authorizationService.refresh().pipe(mergeMap(() => { | |
return this.jwtInterceptor.intercept(req, next); | |
})); | |
} | |
return throwError(err); | |
})); | |
} else { | |
return next.handle(req); | |
} | |
} | |
} |
Thanks you. It helped me a lot. Gave you a star =)
Great and simple solution, thank you for sharing. How will the refresh token flow behave if there are multiple requests at the same time where the token expired? As I understand the code, for every request it would concurrently request a refresh token for each request. You should probably add a state where you keep the refreshSubject on the service as member for as long as it hasn't completed and always return the same subject for subsequent calls. What do you think?
Also interested in this, thanks!
Thank you for sharing. It help a lot!
Great and simple solution, thank you for sharing. How will the refresh token flow behave if there are multiple requests at the same time where the token expired? As I understand the code, for every request it would concurrently request a refresh token for each request. You should probably add a state where you keep the refreshSubject on the service as member for as long as it hasn't completed and always return the same subject for subsequent calls. What do you think?
HAVE YOU FIGURED IT OUT? THANKS
Great and simple solution, thank you for sharing. How will the refresh token flow behave if there are multiple requests at the same time where the token expired? As I understand the code, for every request it would concurrently request a refresh token for each request. You should probably add a state where you keep the refreshSubject on the service as member for as long as it hasn't completed and always return the same subject for subsequent calls. What do you think?
HAVE YOU FIGURED IT OUT? THANKS
This is a server side issue. You should adjust so called 'grace period' for your tokens. This way the tokens will still be accepted for some time after they are expired.
Why does the JWT Interceptor need to be called explicitly? Can't you just declare the refresh token interceptor before the JWT one and then do return next.handle(req)
?
Cool gist, I modified it a little bit, because I'm using NgRx, and added automatic logout on failed refresh.
(If someone knows a way to express that throw-catch-throw mess at the bottom without messing up the types, let me know)
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.jwtInterceptor.isWhitelistedDomain(req) && !this.jwtInterceptor.isBlacklistedRoute(req)) {
return next.handle(req).pipe(
catchError((err: HttpErrorResponse) => {
if (err.status === 401) {
this.authStoreFacade.refresh();
return this.authStoreFacade.refreshFinished$.pipe(
mergeMap(a =>
iif(
() => a.type === refreshSuccess.type,
this.jwtInterceptor.intercept(req, next),
throwError(a.payload).pipe(
catchError((e: HttpErrorResponse) => {
this.authStoreFacade.logout();
return throwError(e);
})
)
)
)
);
}
return throwError(err);
})
);
} else return next.handle(req);
}
Great and simple solution, thank you for sharing. How will the refresh token flow behave if there are multiple requests at the same time where the token expired? As I understand the code, for every request it would concurrently request a refresh token for each request. You should probably add a state where you keep the refreshSubject on the service as member for as long as it hasn't completed and always return the same subject for subsequent calls. What do you think?
HAVE YOU FIGURED IT OUT? THANKS
This is a server side issue. You should adjust so called 'grace period' for your tokens. This way the tokens will still be accepted for some time after they are expired.
This is not a server side issue. If the token is expired any request until the first token response will request a new token request. We need a refreshSubject or something similar to let other consecutive request know a token is pending some how.
Thanks for this very neat solution. I have been using it for a while. However lately i have noticed if the browser sits idle for a little while and then some API calls are sent - sometimes they are missing the authorization header (checked it from server logs). I opened an issue with the jwt token repo. @Toilal Would it be possible for you to check that thread out ( auth0/angular2-jwt#763 )?
Great and simple solution, thank you for sharing. How will the refresh token flow behave if there are multiple requests at the same time where the token expired? As I understand the code, for every request it would concurrently request a refresh token for each request. You should probably add a state where you keep the refreshSubject on the service as member for as long as it hasn't completed and always return the same subject for subsequent calls. What do you think?