diff --git a/README.md b/README.md index 98383302..24df16d7 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ export class AppComponent { ## Configuration Options -### `tokenGetter: function` +### `tokenGetter: function(?HttpRequest)` The `tokenGetter` is a function which returns the user's token. This function simply needs to make a retrieval call to wherever the token is stored. In many cases, the token will be stored in local storage or session storage. @@ -104,6 +104,24 @@ JwtModule.forRoot({ }); ``` +If you have multiple tokens for multiple domains, you can use the `HttpRequest` passed to the `tokenGetter` function to get the correct token for each intercepted request. + +```ts +// ... +JwtModule.forRoot({ + config: { + // ... + tokenGetter: (request) => { + if (request.url.includes("foo")) { + return localStorage.getItem("access_token_foo"); + } + + return localStorage.getItem("access_token"); + }, + }, +}); +``` + ### `whitelistedDomains: array` Authenticated requests should only be sent to domains you know and trust. Many applications make requests to APIs from multiple domains, some of which are not controlled by the developer. Since there is no way to know what the API being called will do with the information contained in the request, it is best to not send the user's token to all APIs in a blind fashion. diff --git a/projects/angular-jwt/src/lib/angular-jwt.module.ts b/projects/angular-jwt/src/lib/angular-jwt.module.ts index a898b6da..d1476b6e 100644 --- a/projects/angular-jwt/src/lib/angular-jwt.module.ts +++ b/projects/angular-jwt/src/lib/angular-jwt.module.ts @@ -1,14 +1,21 @@ -import { NgModule, ModuleWithProviders, Optional, SkipSelf, Provider } from '@angular/core'; -import { HTTP_INTERCEPTORS } from '@angular/common/http'; -import {JwtInterceptor} from './jwt.interceptor'; -import {JWT_OPTIONS} from './jwtoptions.token'; -import {JwtHelperService} from './jwthelper.service'; - +import { + NgModule, + ModuleWithProviders, + Optional, + SkipSelf, + Provider, +} from "@angular/core"; +import { HttpRequest, HTTP_INTERCEPTORS } from "@angular/common/http"; +import { JwtInterceptor } from "./jwt.interceptor"; +import { JWT_OPTIONS } from "./jwtoptions.token"; +import { JwtHelperService } from "./jwthelper.service"; export interface JwtModuleOptions { jwtOptionsProvider?: Provider; config?: { - tokenGetter?: () => string | null | Promise; + tokenGetter?: ( + request?: HttpRequest + ) => string | null | Promise; headerName?: string; authScheme?: string; whitelistedDomains?: Array; @@ -20,10 +27,11 @@ export interface JwtModuleOptions { @NgModule() export class JwtModule { - constructor(@Optional() @SkipSelf() parentModule: JwtModule) { if (parentModule) { - throw new Error('JwtModule is already loaded. It should only be imported in your application\'s main module.'); + throw new Error( + "JwtModule is already loaded. It should only be imported in your application's main module." + ); } } static forRoot(options: JwtModuleOptions): ModuleWithProviders { @@ -33,15 +41,14 @@ export class JwtModule { { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, - multi: true + multi: true, }, - options.jwtOptionsProvider || - { + options.jwtOptionsProvider || { provide: JWT_OPTIONS, - useValue: options.config + useValue: options.config, }, - JwtHelperService - ] + JwtHelperService, + ], }; } } diff --git a/projects/angular-jwt/src/lib/jwt.interceptor.ts b/projects/angular-jwt/src/lib/jwt.interceptor.ts index 75ef38c3..15a7f6c1 100644 --- a/projects/angular-jwt/src/lib/jwt.interceptor.ts +++ b/projects/angular-jwt/src/lib/jwt.interceptor.ts @@ -14,7 +14,9 @@ import { from, Observable } from "rxjs"; @Injectable() export class JwtInterceptor implements HttpInterceptor { - tokenGetter: () => string | null | Promise; + tokenGetter: ( + request?: HttpRequest + ) => string | null | Promise; headerName: string; authScheme: string; whitelistedDomains: Array; @@ -112,7 +114,7 @@ export class JwtInterceptor implements HttpInterceptor { ) { return next.handle(request); } - const token = this.tokenGetter(); + const token = this.tokenGetter(request); if (token instanceof Promise) { return from(token).pipe( diff --git a/src/app/services/example-http.service.spec.ts b/src/app/services/example-http.service.spec.ts index fc43a049..c6a34331 100644 --- a/src/app/services/example-http.service.spec.ts +++ b/src/app/services/example-http.service.spec.ts @@ -7,10 +7,22 @@ import { import { JwtModule } from "angular-jwt"; export function tokenGetter() { - return "SOME_TEST_TOKEN"; + return "TEST_TOKEN"; } -describe("ExampleHttpService", () => { +export function tokenGetterWithRequest(request) { + if (request.url.includes("1")) { + return "TEST_TOKEN_1"; + } + + if (request.url.includes("2")) { + return "TEST_TOKEN_2"; + } + + return "TEST_TOKEN"; +} + +describe("Example HttpService: with simple tokken getter", () => { let service: ExampleHttpService; let httpMock: HttpTestingController; @@ -82,3 +94,52 @@ describe("ExampleHttpService", () => { }) ); }); + +describe("Example HttpService: with request based tokken getter", () => { + let service: ExampleHttpService; + let httpMock: HttpTestingController; + + const routes = [ + `http://example-1.com/api/`, + `http://example-2.com/api/`, + `http://example-3.com/api/`, + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + JwtModule.forRoot({ + config: { + tokenGetter: tokenGetterWithRequest, + whitelistedDomains: [ + "example-1.com", + "example-2.com", + "example-3.com", + ], + }, + }), + ], + }); + service = TestBed.get(ExampleHttpService); + httpMock = TestBed.get(HttpTestingController); + }); + + it("should add Authorisation header", () => { + expect(service).toBeTruthy(); + }); + + routes.forEach((route) => + it(`should set the correct auth token for a domain: ${route}`, () => { + service.testRequest(route).subscribe((response) => { + expect(response).toBeTruthy(); + }); + + const httpRequest = httpMock.expectOne(route); + expect(httpRequest.request.headers.has("Authorization")).toEqual(true); + expect(httpRequest.request.headers.get("Authorization")).toEqual( + `Bearer ${tokenGetterWithRequest({ url: route })}` + ); + }) + ); +});