From 6043327f5c3357869ac05de99674fa35d52a842d Mon Sep 17 00:00:00 2001 From: Ryan Chenkie Date: Mon, 31 Jul 2017 17:30:26 -0400 Subject: [PATCH] handle async tokenGetter and custom factory function --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++ index.ts | 6 ++-- package.json | 2 +- src/jwt.interceptor.ts | 54 +++++++++++++++++++++---------- 4 files changed, 115 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1e6af102..8d2ba8f3 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,79 @@ JwtModule.forRoot({ }) ``` +## Using a Custom Options Factory Function + +In some cases, you may need to provide a custom factory function to properly handle your configuration options. This is the case if your `tokenGetter` function relies on a service or if you are using an asynchronous storage mechanism (like Ionic's `Storage`). + +Import the `JWT_OPTIONS` `InjectionToken` so that you can instruct it to use your custom factory function. + +Create a factory function and specify the options as you normally would if you were using `JwtModule.forRoot` directly. If you need to use a service in the function, list it as a parameter in the function and pass it in the `deps` array when you provide the function. + + +```ts +import { JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { TokenService } from './app.tokenservice'; + +// ... + +export function jwtOptionsFactory(tokenService) { + return { + tokenGetter: () => { + return tokenService.getAsyncToken(); + } + } +} + +// ... + +@NgModule({ + // ... + imports: [ + JwtModule.forRoot({ + jwtOptionsProvider: { + provide: JWT_OPTIONS, + useFactory: jwtOptionsFactory, + deps: [TokenService] + } + }) + ], + providers: [TokenService] +}) +``` + +## Configuration for Ionic 2+ + +The custom factory function approach described above can be used to get a token asynchronously with Ionic's `Storage`. + +```ts +import { JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { Storage } from '@ionic/storage'; + +const storage = new Storage(); + +export function jwtOptionsFactory() { + return { + tokenGetter: () => { + return storage.get('access_token'); + } + } +} + +// ... + +@NgModule({ + // ... + imports: [ + JwtModule.forRoot({ + jwtOptionsProvider: { + provide: JWT_OPTIONS, + useFactory: jwtOptionsFactory + } + }) + ] +}) +``` + ## What is Auth0? Auth0 helps you to: diff --git a/index.ts b/index.ts index 312dba05..00250973 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,4 @@ -import { NgModule, ModuleWithProviders, Optional, SkipSelf } from '@angular/core'; +import { NgModule, ModuleWithProviders, Optional, SkipSelf, Provider } from '@angular/core'; import { JwtInterceptor } from './src/jwt.interceptor'; import { JwtHelperService } from './src/jwthelper.service'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; @@ -9,8 +9,9 @@ export * from './src/jwthelper.service'; export * from './src/jwtoptions.token'; export interface JwtModuleOptions { + jwtOptionsProvider?: Provider, config: { - tokenGetter: () => string; + tokenGetter?: () => string | Promise; headerName?: string; authScheme?: string; whitelistedDomains?: Array; @@ -36,6 +37,7 @@ export class JwtModule { useClass: JwtInterceptor, multi: true }, + options.jwtOptionsProvider || { provide: JWT_OPTIONS, useValue: options.config diff --git a/package.json b/package.json index 60661543..bd99d712 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@auth0/angular-jwt", - "version": "1.0.0-beta.7", + "version": "1.0.0-beta.8", "description": "JSON Web Token helper library for Angular", "scripts": { "prepublish": "ngc && npm run build", diff --git a/src/jwt.interceptor.ts b/src/jwt.interceptor.ts index 00d3bc2e..b8d0a09e 100644 --- a/src/jwt.interceptor.ts +++ b/src/jwt.interceptor.ts @@ -5,13 +5,15 @@ import { HttpEvent, HttpInterceptor } from '@angular/common/http'; -import { Observable } from 'rxjs/Observable'; import { JwtHelperService } from './jwthelper.service'; import { JWT_OPTIONS } from './jwtoptions.token'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/fromPromise'; +import 'rxjs/add/operator/mergeMap'; @Injectable() export class JwtInterceptor implements HttpInterceptor { - tokenGetter: () => string; + tokenGetter: () => string | Promise; headerName: string; authScheme: string; whitelistedDomains: Array; @@ -24,7 +26,10 @@ export class JwtInterceptor implements HttpInterceptor { ) { this.tokenGetter = config.tokenGetter; this.headerName = config.headerName || 'Authorization'; - this.authScheme = (config.authScheme || config.authScheme === '') ? config.authScheme : 'Bearer '; + this.authScheme = + config.authScheme || config.authScheme === '' + ? config.authScheme + : 'Bearer '; this.whitelistedDomains = config.whitelistedDomains || []; this.throwNoTokenError = config.throwNoTokenError || false; this.skipWhenExpired = config.skipWhenExpired; @@ -34,10 +39,14 @@ export class JwtInterceptor implements HttpInterceptor { let requestUrl: URL; try { requestUrl = new URL(request.url); - return this.whitelistedDomains.findIndex(domain => - typeof domain === 'string' ? domain === requestUrl.host : - domain instanceof RegExp ? domain.test(requestUrl.host) : false - ) > -1; + return ( + this.whitelistedDomains.findIndex( + domain => + typeof domain === 'string' + ? domain === requestUrl.host + : domain instanceof RegExp ? domain.test(requestUrl.host) : false + ) > -1 + ); } catch (err) { // if we're here, the request is made // to the same domain as the Angular app @@ -46,26 +55,22 @@ export class JwtInterceptor implements HttpInterceptor { } } - intercept( + handleInterception( + token: string, request: HttpRequest, next: HttpHandler - ): Observable> { - const token = this.tokenGetter(); - let tokenIsExpired; + ) { + let tokenIsExpired: boolean; if (!token && this.throwNoTokenError) { throw new Error('Could not get token from tokenGetter function.'); } if (this.skipWhenExpired) { - tokenIsExpired = token ? this.jwtHelper.isTokenExpired() : true; + tokenIsExpired = token ? this.jwtHelper.isTokenExpired(token) : true; } - if ( - token && - tokenIsExpired && - this.skipWhenExpired - ) { + if (token && tokenIsExpired && this.skipWhenExpired) { request = request.clone(); } else if (token && this.isWhitelistedDomain(request)) { request = request.clone({ @@ -76,4 +81,19 @@ export class JwtInterceptor implements HttpInterceptor { } return next.handle(request); } + + intercept( + request: HttpRequest, + next: HttpHandler + ): Observable> { + const token: any = this.tokenGetter(); + + if (token instanceof Promise) { + return Observable.fromPromise(token).mergeMap((asyncToken: string) => { + return this.handleInterception(asyncToken, request, next); + }); + } else { + return this.handleInterception(token, request, next); + } + } }