I was working with the Http
clase from Angular but I decide to make the migration and work with the new HttpClient
, and I was trying to create a solution with Interceptors
to manage the cases when I need to refresh the token and when I need to modify the header to put the Authorization Token.
First I found these post and so many others :
... but those solutions are perfect if you just want to handle the action to put the Authorization Header. Then I come up with this solution
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector, private authService: Auth) {
}
private getRequestWithAuthentication(request: HttpRequest<any>, next: HttpHandler, auth: OAuthService): Observable<HttpEvent<any>> {
const req = request.clone({
headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
});
return next.handle(req);
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// To avoid cyclic dependency
const auth = this.injector.get(OAuthService);
if (auth.hasAuthorization()) {
return this.getRequestWithAuthentication(request, next, auth);
} else if (auth.hasAuthorizationRefresh() && request.url !== AUTHORIZE_URL) {
return auth.refreshToken().flatMap(
(res: any) => {
auth.saveTokens(res);
return this.getRequestWithAuthentication(request, next, auth);
}
).catch(() => {
return next.handle(request);
});
} else if (request.url === AUTHORIZE_URL) {
return next.handle(request);
}
return this.getRequestWithAuthentication(request, next, auth);
}
}
The main idea with this is simple:
HttpClient
in the OAuthService
so It's gonna pass from the interceptor too and It's gonna make a infinite loop if I don't check it.This Solution work fine in some cases, but the thing is when for example the token expired and you have multiple request, every request is going to try to refresh the token.
After this I found this solution but I wanna know what do you think about code and way that I'm doing it.
Ok, First I created a Service to save the state of the refresh token request and Observable to know when the request is done.
@Injectable()
export class RefreshTokenService {
public processing: boolean = false;
public storage: Subject<any> = new Subject<any>();
public publish(value: any) {
this.storage.next(value);
}
}
I noticed that It was better if I have two Interceptors one to refresh the token and handle that and one to put the Authorization Header if exist.
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector, private tokenService: RefreshTokenService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const auth = this.injector.get(OAuthService);
if (!auth.hasAuthorization() && auth.hasAuthorizationRefresh() && !this.tokenService.processing && request.url !== AUTHORIZE_URL) {
this.tokenService.processing = true;
return auth.refreshToken().flatMap(
(res: any) => {
auth.saveTokens(res);
this.tokenService.publish(res);
this.tokenService.processing = false;
return next.handle(request);
}
).catch(() => {
this.tokenService.publish({});
this.tokenService.processing = false;
return next.handle(request);
});
} else if (request.url === AUTHORIZE_URL) {
return next.handle(request);
}
if (this.tokenService.processing) {
return this.tokenService.storage.flatMap(
() => {
return next.handle(request);
}
);
} else {
return next.handle(request);
}
}
}
So here I'm waiting to the refresh token to be available or fails and then I release the request that needs the Authorization Header.
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const auth = this.injector.get(OAuthService);
let req = request;
if (auth.hasAuthorization()) {
req = request.clone({
headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
});
}
return next.handle(req).do(
() => {},
(error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
auth.logOut();
}
}
});
}
}
@NgModule({
imports: [
...,
HttpClientModule
],
declarations: [
...
],
providers: [
...
OAuthService,
AuthService,
RefreshTokenService,
{
provide: HTTP_INTERCEPTORS,
useClass: RefreshTokenInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
Please any feedback will be welcome and if I'm doning something wrong tell me. I'm testing with Angular 4.4.6 but I don't know if it work on angular 5, I think should work.