Last active
July 4, 2020 18:29
-
-
Save alxhub/1fa85d0b8ef0286b617566e65a0011b4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | |
} | |
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
hello guys, were you able to fix that infinite loop or is there some workaround to get this approach working?