Skip to content

Instantly share code, notes, and snippets.

@soap
Forked from Toilal/api.module.ts
Created March 23, 2019 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soap/9eae2d81d2fd20fa75fd93cd94ce2084 to your computer and use it in GitHub Desktop.
Save soap/9eae2d81d2fd20fa75fd93cd94ce2084 to your computer and use it in GitHub Desktop.
@auth0/angular2-jwt Authorization Service and HttpInterceptor supporting JWT Refresh Token (Angular 4.3+ & 5+)
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { JWT_OPTIONS, JwtInterceptor, JwtModule } from '@auth0/angular-jwt';
import { AuthorizationService } from './authorization.service';
import { environment } from '../../environments/environment';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { RefreshTokenInterceptor } from './refresh-token-interceptor';
function jwtOptionsFactory (authorizationService: AuthorizationService) {
return {
tokenGetter: () => {
return authorizationService.getAccessToken();
},
blacklistedRoutes: [`${environment.apiBaseUrl}/login-check`]
};
}
@NgModule({
imports: [
CommonModule,
HttpClientModule,
JwtModule.forRoot({
jwtOptionsProvider: {
provide: JWT_OPTIONS,
useFactory: jwtOptionsFactory,
deps: [AuthorizationService]
}
})
],
providers: [
AuthorizationService,
JwtInterceptor, // Providing JwtInterceptor allow to inject JwtInterceptor manually into RefreshTokenInterceptor
{
provide: HTTP_INTERCEPTORS,
useExisting: JwtInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: RefreshTokenInterceptor,
multi: true
}
],
declarations: []
})
export class ApiModule {
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { environment } from '../../environments/environment';
import { Observable, ReplaySubject } from 'rxjs';
import { LoginResponse } from './login-response';
@Injectable({
providedIn: 'root'
})
export class AuthorizationService {
constructor (private httpClient: HttpClient) {
}
loginCheckUrl = `${environment.apiBaseUrl}/login-check`;
refreshTokenUrl = `${environment.apiBaseUrl}/refresh-token`;
login (username: string, password: string): Observable<LoginResponse> {
const body = new HttpParams()
.set('_username', username)
.set('_password', password);
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
const postObservable = this.httpClient.post<LoginResponse>(this.loginCheckUrl, body.toString(), { headers });
const subject = new ReplaySubject<LoginResponse>(1);
subject.subscribe((r: LoginResponse) => {
this.setAccessToken(r.token);
this.setRefreshToken(r.refresh_token);
}, (err) => {
this.handleAuthenticationError(err);
});
postObservable.subscribe(subject);
return subject;
}
refresh (): Observable<LoginResponse> {
const body = new HttpParams().set('refresh_token', this.getRefreshToken());
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
const refreshObservable = this.httpClient.post<LoginResponse>(this.refreshTokenUrl, body.toString(), { headers });
const refreshSubject = new ReplaySubject<LoginResponse>(1);
refreshSubject.subscribe((r: LoginResponse) => {
this.setAccessToken(r.token);
this.setRefreshToken(r.refresh_token);
}, (err) => {
this.handleAuthenticationError(err);
});
refreshObservable.subscribe(refreshSubject);
return refreshSubject;
}
logout () {
this.setAccessToken(null);
this.setRefreshToken(null);
}
isAuthenticated (): boolean {
return !!this.getAccessToken();
}
private handleAuthenticationError (err: any) {
// TODO: Only for authentication error codes
this.setAccessToken(null);
this.setRefreshToken(null);
}
private setAccessToken (accessToken: string) {
if (!accessToken) {
localStorage.removeItem('access_token');
} else {
localStorage.setItem('access_token', accessToken);
}
}
private setRefreshToken (refreshToken: string) {
if (!refreshToken) {
localStorage.removeItem('refresh_token');
} else {
localStorage.setItem('refresh_token', refreshToken);
}
}
getAccessToken () {
return localStorage.getItem('access_token');
}
getRefreshToken () {
return localStorage.getItem('refresh_token');
}
}
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError, mergeMap } from 'rxjs/operators';
import { AuthorizationService } from './authorization.service';
import { JwtInterceptor } from '@auth0/angular-jwt';
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
constructor (private authorizationService: AuthorizationService, private jwtInterceptor: JwtInterceptor) {
}
intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.jwtInterceptor.isWhitelistedDomain(req) && !this.jwtInterceptor.isBlacklistedRoute(req)) {
return next.handle(req).pipe(
catchError((err) => {
const errorResponse = err as HttpErrorResponse;
if (errorResponse.status === 401 && errorResponse.error.message === 'Expired JWT Token') {
return this.authorizationService.refresh().pipe(mergeMap(() => {
return this.jwtInterceptor.intercept(req, next);
}));
}
return throwError(err);
}));
} else {
return next.handle(req);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment