Skip to content

Instantly share code, notes, and snippets.

@kctang
Created January 25, 2019 10:17
Show Gist options
  • Save kctang/6a96f77e646ffa60f3d5504d1e44f7ed to your computer and use it in GitHub Desktop.
Save kctang/6a96f77e646ffa60f3d5504d1e44f7ed to your computer and use it in GitHub Desktop.
AuthHttpInterceptor.ts
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