Skip to content

Instantly share code, notes, and snippets.

@JonatanFlores
Forked from alxhub/auth-interceptor-example.ts
Last active January 18, 2018 17:59
Show Gist options
  • Save JonatanFlores/7eaf2c5d5eb2280c53b73aa8626440ed to your computer and use it in GitHub Desktop.
Save JonatanFlores/7eaf2c5d5eb2280c53b73aa8626440ed to your computer and use it in GitHub Desktop.
import { HttpClient, HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import "rxjs/add/observable/defer";
import "rxjs/add/observable/concat";
import "rxjs/add/operator/concatMap";
import "rxjs/add/operator/catch";
import "rxjs/add/operator/filter";
import "rxjs/add/operator/take";
import "rxjs/add/operator/ignoreElements";
import "rxjs/add/operator/shareReplay";
abstract class AuthService {
// Subject tracks the current token, or is null if no token is currently
// available (e.g. refresh pending).
private subject = new BehaviorSubject<string|null>(null);
readonly refreshToken: Observable<any>;
readonly token: Observable<string>;
constructor() {
// refreshToken, when subscribed, gets the new token from the backend,
// and then completes without values.
this.refreshToken = Observable.defer(() => {
// Defer allows us to easily execute some action when the Observable
// is subscribed. Here, we set the current token to `null` until the
// refresh operation is complete. This ensures no requests will be
// sent with a known bad token.
this.subject.next(null);
return this
// Next, we refresh the token from the server.
.doRefreshToken()
// Set it as the active token.
.do(token => this.subject.next(token))
// Drop the value, ensuring this Observable only completes when
// done and doesn't emit.
.ignoreElements()
// Finally, share the Observable so we don't attempt multiple
// refreshes at once.
.shareReplay();
});
// token, when subscribed, returns the latest token.
this.token = this
// Read the subject (stream of tokens).
.subject
// Filter out the `null` ones. This part ensure we wait for the next
// good token.
.filter(token => token !== null)
// Take the next good token.
.take(1);
// There's no current token to start, so refresh to start with. Optionally,
// we could set token up to refresh on the first subscription.
this.refreshToken.subscribe();
}
// Actually refresh the token. Left up to the user.
abstract doRefreshToken(): Observable<string>;
}
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) { }
private addToken(req: HttpRequest<any>): HttpRequest<any> {
return req.clone({ headers: req.headers.set('Authorization', `Bearer ${this.auth.getToken()}`) });
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this
.auth
// Get the latest token from the auth service.
.token
// Map the token to a request with the right header set.
.map(token => req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) }))
// Execute the request on the server.
.concatMap(authReq => next.handle(authReq))
// Catch the 401 and handle it by refreshing the token and restarting the chain
// (where a new subscription to this.auth.token will get the latest token).
.catch((err, restart) => {
// If the request is unauthorized, try refreshing the token before restarting.
if (err instanceof HttpErrorResponse && err.status === 401) {
return Observable.concat(this.auth.refreshToken, restart);
}
throw err;
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment