Created
January 25, 2019 10:17
-
-
Save kctang/6a96f77e646ffa60f3d5504d1e44f7ed to your computer and use it in GitHub Desktop.
AuthHttpInterceptor.ts
This file contains hidden or 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
import { | |
HttpEvent, HttpEventType, HttpHandler, HttpInterceptor, HttpRequest | |
} from '@angular/common/http' | |
import { Injectable } from '@angular/core' | |
import { Observable, of } from 'rxjs' | |
import { concatMap, take } from 'rxjs/operators' | |
// --- NGXS stuff to read/write access token and refresh token | |
// --- replace it with your own implementation if you are not using NGXS | |
import { Select, Store } from '@ngxs/store' | |
import { AuthState } from '../auth/states/Auth.state' | |
import { AuthStateActions } from '../auth/states/Auth.state.actions' | |
/** | |
* Authentication HTTP interceptor that will: | |
* | |
* 1. Set authorization HTTP header before making a request. | |
* 2. Update expired access token and retry failed request. | |
*/ | |
@Injectable() | |
export class AuthHttpInterceptor implements HttpInterceptor { | |
// this is NGXS's way to retrieve current access token as an observable | |
@Select(AuthState.accessToken) | |
accessToken$!: Observable<string> | |
// retrieve refresh token | |
@Select(AuthState.refreshToken) | |
refreshToken$!: Observable<string> | |
constructor (private store: Store) { | |
} | |
intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
// get current access token value | |
return this.accessToken$.pipe( | |
take(1), | |
// --- if access token available, set it to header before sending request to server | |
concatMap(accessToken => { | |
if (accessToken) { | |
return next.handle(req.clone({ | |
setHeaders: { Authorization: `Bearer ${accessToken}` } | |
})) | |
} | |
return next.handle(req) | |
}), | |
// --- login with refresh token on UNAUTHENTICATED graphql error, then try again. | |
// Your Apollo server should throw AuthenticationError if an access token has expired. | |
// For more info, see: https://www.apollographql.com/docs/apollo-server/features/errors.html | |
concatMap(event => { | |
let needToAuthenticate = false | |
if ( | |
event.type === HttpEventType.Response && | |
event.status === 200 && | |
event.body && | |
Array.isArray(event.body.errors) | |
) { | |
const errors = event.body.errors as any[] | |
needToAuthenticate = !!errors.find(e => e.extensions && e.extensions.code === 'UNAUTHENTICATED') | |
} | |
if (needToAuthenticate) { | |
// update access token by logging in to your auth server using a refresh token | |
return this.refreshToken$.pipe( | |
take(1), | |
concatMap(refreshToken => this.store.dispatch(new AuthStateActions.LoginWithRefreshToken(refreshToken))), | |
// the previous LoginWithRefreshToken should have updated access token's value | |
// get the value and retry the failed request | |
concatMap(() => this.accessToken$.pipe(take(1))), | |
concatMap(accessToken => { | |
if (accessToken) { | |
return next.handle(req.clone({ | |
setHeaders: { Authorization: `Bearer ${accessToken}` } | |
})) | |
} else { | |
// if logging in with refresh token failed to update access token, then can't help it... | |
// --- apollo-link does not understand return throwError('message') so just throw new error | |
throw new Error('Error getting access token after logging in with refresh token') | |
} | |
}) | |
) | |
} | |
return of(event) | |
}) | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment