├── .version ├── src ├── assets │ ├── .gitkeep │ └── example-resource.json ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── styles.scss ├── tsconfig.app.json ├── tsconfig.spec.json ├── index.html ├── app │ ├── services │ │ ├── example-http.service.ts │ │ └── example-http.service.spec.ts │ ├── app.component.ts │ └── app.module.ts ├── tslint.json ├── main.ts ├── test.ts ├── karma.conf.js └── polyfills.ts ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── Feature Request.yml │ └── Bug Report.yml ├── actions │ ├── get-version │ │ └── action.yml │ ├── get-prerelease │ │ └── action.yml │ ├── tag-exists │ │ └── action.yml │ ├── release-create │ │ └── action.yml │ ├── npm-publish │ │ └── action.yml │ └── get-release-notes │ │ └── action.yml ├── workflows │ ├── semgrep.yml │ ├── release.yml │ ├── build.yml │ ├── snyk.yml │ └── npm-release.yml ├── stale.yml └── PULL_REQUEST_TEMPLATE.md ├── opslevel.yml ├── .shiprc ├── projects └── angular-jwt │ ├── src │ ├── lib │ │ ├── jwtoptions.token.ts │ │ ├── angular-jwt.module.ts │ │ ├── jwt.interceptor.ts │ │ ├── jwthelper.service.ts │ │ └── jwthelper.service.spec.ts │ ├── index.d.ts │ ├── index.ts │ ├── test.ts │ └── index.test-d.ts │ ├── ng-package.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── package.json │ ├── .eslintrc.json │ └── karma.conf.js ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.e2e.json └── protractor.conf.js ├── .editorconfig ├── .browserslistrc ├── CONTRIBUTING.md ├── tsconfig.json ├── .gitignore ├── LICENSE ├── .eslintrc.json ├── package.json ├── EXAMPLES.md ├── angular.json ├── CHANGELOG.md ├── API.md └── README.md /.version: -------------------------------------------------------------------------------- 1 | v5.2.0 -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @auth0/project-dx-sdks-engineer-codeowner 2 | -------------------------------------------------------------------------------- /src/assets/example-resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok" 3 | } 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/angular2-jwt/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /opslevel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | repository: 4 | owner: dx_sdks 5 | tier: 6 | tags: 7 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /.shiprc: -------------------------------------------------------------------------------- 1 | { 2 | "packagePath": "projects/angular-jwt", 3 | "files": { 4 | ".version": [] 5 | }, 6 | "postbump": "npm run build" 7 | } 8 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/lib/jwtoptions.token.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | export const JWT_OPTIONS = new InjectionToken('JWT_OPTIONS'); 4 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/jwt.interceptor'; 2 | export * from './lib/jwthelper.service'; 3 | export * from './lib/jwtoptions.token'; 4 | export * from './lib/angular-jwt.module'; 5 | -------------------------------------------------------------------------------- /projects/angular-jwt/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/angular-jwt", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Help & Questions 4 | url: https://community.auth0.com 5 | about: Ask general support or usage questions in the Auth0 Community forums 6 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /projects/angular-jwt/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of angular-jwt 3 | */ 4 | 5 | export * from './lib/jwt.interceptor'; 6 | export * from './lib/jwthelper.service'; 7 | export * from './lib/jwtoptions.token'; 8 | export * from './lib/angular-jwt.module'; 9 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /projects/angular-jwt/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular2Jwt 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getTitleText()).toEqual('Welcome to angular2-jwt!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/services/example-http.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | @Injectable({ 5 | providedIn: 'root', 6 | }) 7 | export class ExampleHttpService { 8 | constructor(private http: HttpClient) {} 9 | 10 | testRequest(route = '/assets/example-resource.json') { 11 | return this.http.get(route); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ExampleHttpService } from './services/example-http.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | template: `
 {{ res$ | async | json }} 
`, 7 | }) 8 | export class AppComponent { 9 | res$ = this.exampleHttpService.testRequest(); 10 | constructor(private exampleHttpService: ExampleHttpService) {} 11 | } 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /.github/actions/get-version/action.yml: -------------------------------------------------------------------------------- 1 | name: Return the version extracted from the branch name 2 | 3 | # 4 | # Returns the version from the .version file. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | outputs: 10 | version: 11 | value: ${{ steps.get_version.outputs.VERSION }} 12 | 13 | runs: 14 | using: composite 15 | 16 | steps: 17 | - id: get_version 18 | shell: bash 19 | run: | 20 | VERSION=$(head -1 .version) 21 | echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | 3 | Please read [Auth0's contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md). 4 | 5 | ## Documentation 6 | 7 | - PR for docs site update, if needed 8 | - Code-level documentation expectations 9 | - 100% documentation coverage for PRs 10 | - Include links to relevant Auth0 doc pages 11 | 12 | ## Code quality tools 13 | 14 | Running `npm run-script build` will perform necessary linting via tsLint and the project-adopted rules contained in `tslint.json` 15 | -------------------------------------------------------------------------------- /projects/angular-jwt/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declarationMap": true, 6 | "target": "es2020", 7 | "module": "es2015", 8 | "moduleResolution": "node", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "inlineSources": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2018" 18 | ] 19 | }, 20 | "exclude": [ 21 | "src/test.ts", 22 | "**/*.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | name: semgrep 2 | 3 | on: 4 | pull_request: 5 | merge_group: 6 | 7 | push: 8 | branches: [main] 9 | 10 | schedule: 11 | - cron: '30 0 1,15 * *' 12 | 13 | jobs: 14 | scan: 15 | runs-on: ubuntu-latest 16 | container: 17 | image: returntocorp/semgrep 18 | 19 | # Skip any PR created by dependabot to avoid permission issues 20 | if: (github.actor != 'dependabot[bot]') 21 | steps: 22 | - uses: actions/checkout@main 23 | - name: Run Semgrep to check for vulnerabilities 24 | run: semgrep ci 25 | env: 26 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_TOKEN }} 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "downlevelIteration": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es2020", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "angular-jwt": [ 23 | "projects/angular-jwt/src/index" 24 | ], 25 | "angular-jwt/*": [ 26 | "projects/angular-jwt/src/*" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting(), { 16 | teardown: { destroyAfterEach: false } 17 | } 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | 31 | # misc 32 | /.angular/cache 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /projects/angular-jwt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@auth0/angular-jwt", 3 | "version": "5.2.0", 4 | "description": "JSON Web Token helper library for Angular", 5 | "private": false, 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/auth0/angular2-jwt" 9 | }, 10 | "author": "Sam Bellen", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/auth0/angular2-jwt/issues" 14 | }, 15 | "keywords": [ 16 | "angular", 17 | "angular 2", 18 | "authentication", 19 | "jwt" 20 | ], 21 | "homepage": "https://github.com/auth0/angular2-jwt", 22 | "peerDependencies": { 23 | "@angular/common": ">=14.0.0" 24 | }, 25 | "dependencies": { 26 | "tslib": "^2.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es/reflect'; 4 | import 'zone.js'; 5 | import 'zone.js/testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting(), { 18 | teardown: { destroyAfterEach: false } 19 | } 20 | ); 21 | // Then we find all the tests. 22 | const context = require.context('./', true, /\.spec\.ts$/); 23 | // And load the modules. 24 | context.keys().map(context); 25 | -------------------------------------------------------------------------------- /.github/actions/get-prerelease/action.yml: -------------------------------------------------------------------------------- 1 | name: Return a boolean indicating if the version contains prerelease identifiers 2 | 3 | # 4 | # Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | version: 11 | required: true 12 | 13 | outputs: 14 | prerelease: 15 | value: ${{ steps.get_prerelease.outputs.PRERELEASE }} 16 | 17 | runs: 18 | using: composite 19 | 20 | steps: 21 | - id: get_prerelease 22 | shell: bash 23 | run: | 24 | if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then 25 | echo "PRERELEASE=true" >> $GITHUB_OUTPUT 26 | else 27 | echo "PRERELEASE=false" >> $GITHUB_OUTPUT 28 | fi 29 | env: 30 | VERSION: ${{ inputs.version }} 31 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { AppComponent } from './app.component'; 4 | import { JwtModule, JWT_OPTIONS } from 'angular-jwt'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | export function tokenGetter() { 8 | return 'SOME_TOKEN'; 9 | } 10 | 11 | export function getAuthScheme(request) { 12 | return 'Bearer '; 13 | } 14 | 15 | export function jwtOptionsFactory() { 16 | return { 17 | tokenGetter, 18 | authScheme: getAuthScheme, 19 | }; 20 | } 21 | 22 | @NgModule({ 23 | declarations: [AppComponent], 24 | imports: [ 25 | BrowserModule, 26 | HttpClientModule, 27 | JwtModule.forRoot({ 28 | jwtOptionsProvider: { 29 | provide: JWT_OPTIONS, 30 | useFactory: jwtOptionsFactory, 31 | }, 32 | }), 33 | ], 34 | providers: [], 35 | bootstrap: [AppComponent], 36 | }) 37 | export class AppModule {} 38 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('@angular-devkit/build-angular/plugins/karma') 13 | ], 14 | client: { 15 | clearContext: false // leave Jasmine Spec Runner output visible in browser 16 | }, 17 | reporters: ['progress', 'kjhtml'], 18 | port: 9876, 19 | colors: true, 20 | logLevel: config.LOG_INFO, 21 | autoWatch: true, 22 | browsers: ['Chrome'], 23 | customLaunchers: { 24 | ChromeHeadlessCI: { 25 | base: 'ChromeHeadless', 26 | flags: ['--no-sandbox'], 27 | }, 28 | }, 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 90 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | daysUntilClose: 7 8 | 9 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 10 | exemptLabels: [] 11 | 12 | # Set to true to ignore issues with an assignee (defaults to false) 13 | exemptAssignees: true 14 | 15 | # Label to use when marking as stale 16 | staleLabel: closed:stale 17 | 18 | # Comment to post when marking as stale. Set to `false` to disable 19 | markComment: > 20 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create npm and GitHub Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | id-token: write # For publishing to npm using --provenance 12 | 13 | ### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public. 14 | ### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `npm-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public. 15 | ### TODO: Also remove `npm-release` workflow from this repo's .github/workflows folder once the repo is public. 16 | 17 | jobs: 18 | release: 19 | uses: ./.github/workflows/npm-release.yml 20 | with: 21 | node-version: 18 22 | require-build: true 23 | release-directory: './dist/angular-jwt' 24 | secrets: 25 | npm-token: ${{ secrets.NPM_TOKEN }} 26 | github-token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /projects/angular-jwt/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "projects/angular-jwt/tsconfig.lib.json", 14 | "projects/angular-jwt/tsconfig.spec.json" 15 | ], 16 | "createDefaultProgram": true 17 | }, 18 | "rules": { 19 | "@angular-eslint/directive-selector": [ 20 | "error", 21 | { 22 | "type": "attribute", 23 | "prefix": "", 24 | "style": "camelCase" 25 | } 26 | ], 27 | "@angular-eslint/component-selector": [ 28 | "error", 29 | { 30 | "type": "element", 31 | "prefix": "", 32 | "style": "kebab-case" 33 | } 34 | ] 35 | } 36 | }, 37 | { 38 | "files": [ 39 | "*.html" 40 | ], 41 | "rules": {} 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | merge_group: 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - main 9 | push: 10 | branches: 11 | - main 12 | 13 | permissions: 14 | contents: read 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.ref }} 18 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 19 | 20 | env: 21 | NODE_VERSION: 18 22 | CACHE_KEY: '${{ github.ref }}-${{ github.run_id }}-${{ github.run_attempt }}' 23 | 24 | jobs: 25 | build: 26 | name: Build Package 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v4 32 | - name: Setup Node 33 | uses: actions/setup-node@v4 34 | with: 35 | node-version: 18 36 | - run: npm ci 37 | - run: npm run lint 38 | - run: npm run build 39 | - run: npm run test:ci 40 | - run: npm run test:types 41 | - name: Upload coverage 42 | uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d 43 | -------------------------------------------------------------------------------- /.github/actions/tag-exists/action.yml: -------------------------------------------------------------------------------- 1 | name: Return a boolean indicating if a tag already exists for the repository 2 | 3 | # 4 | # Returns a simple true/false boolean indicating whether the tag exists or not. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | token: 11 | required: true 12 | tag: 13 | required: true 14 | 15 | outputs: 16 | exists: 17 | description: 'Whether the tag exists or not' 18 | value: ${{ steps.tag-exists.outputs.EXISTS }} 19 | 20 | runs: 21 | using: composite 22 | 23 | steps: 24 | - id: tag-exists 25 | shell: bash 26 | run: | 27 | GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" 28 | http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") 29 | if [ "$http_status_code" -ne "404" ] ; then 30 | echo "EXISTS=true" >> $GITHUB_OUTPUT 31 | else 32 | echo "EXISTS=false" >> $GITHUB_OUTPUT 33 | fi 34 | env: 35 | TAG_NAME: ${{ inputs.tag }} 36 | GITHUB_TOKEN: ${{ inputs.token }} 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Auth0 Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/snyk.yml: -------------------------------------------------------------------------------- 1 | name: Snyk 2 | 3 | on: 4 | merge_group: 5 | workflow_dispatch: 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | push: 11 | branches: 12 | - main 13 | schedule: 14 | - cron: '30 0 1,15 * *' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 21 | cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} 22 | 23 | jobs: 24 | check: 25 | name: Check for Vulnerabilities 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' 30 | run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. 31 | 32 | - uses: actions/checkout@v4 33 | with: 34 | ref: ${{ github.event.pull_request.head.sha || github.ref }} 35 | 36 | - uses: snyk/actions/node@b98d498629f1c368650224d6d212bf7dfa89e4bf # pin@0.4.0 37 | env: 38 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 39 | -------------------------------------------------------------------------------- /projects/angular-jwt/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-coverage'), 11 | require('karma-chrome-launcher'), 12 | require('karma-jasmine-html-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageReporter: { 19 | type : 'lcov', 20 | dir : require('path').join(__dirname, '../../coverage'), 21 | }, 22 | reporters: ['progress', 'kjhtml', 'coverage'], 23 | port: 9876, 24 | colors: true, 25 | logLevel: config.LOG_INFO, 26 | autoWatch: true, 27 | browsers: ['Chrome'], 28 | customLaunchers: { 29 | ChromeHeadlessCI: { 30 | base: 'ChromeHeadless', 31 | flags: ['--no-sandbox'], 32 | }, 33 | }, 34 | singleRun: false 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /.github/actions/release-create/action.yml: -------------------------------------------------------------------------------- 1 | name: Create a GitHub release 2 | 3 | # 4 | # Creates a GitHub release with the given version. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | token: 11 | required: true 12 | files: 13 | required: false 14 | name: 15 | required: true 16 | body: 17 | required: true 18 | tag: 19 | required: true 20 | commit: 21 | required: true 22 | draft: 23 | default: false 24 | required: false 25 | prerelease: 26 | default: false 27 | required: false 28 | fail_on_unmatched_files: 29 | default: true 30 | required: false 31 | 32 | runs: 33 | using: composite 34 | 35 | steps: 36 | - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 37 | with: 38 | body: ${{ inputs.body }} 39 | name: ${{ inputs.name }} 40 | tag_name: ${{ inputs.tag }} 41 | target_commitish: ${{ inputs.commit }} 42 | draft: ${{ inputs.draft }} 43 | prerelease: ${{ inputs.prerelease }} 44 | fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} 45 | files: ${{ inputs.files }} 46 | env: 47 | GITHUB_TOKEN: ${{ inputs.token }} 48 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json", 14 | "e2e/tsconfig.json" 15 | ], 16 | "createDefaultProgram": true 17 | }, 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates" 21 | ], 22 | "rules": { 23 | "@angular-eslint/component-selector": [ 24 | "error", 25 | { 26 | "prefix": "app", 27 | "style": "kebab-case", 28 | "type": "element" 29 | } 30 | ], 31 | "@angular-eslint/directive-selector": [ 32 | "error", 33 | { 34 | "prefix": "app", 35 | "style": "camelCase", 36 | "type": "attribute" 37 | } 38 | ] 39 | } 40 | }, 41 | { 42 | "files": [ 43 | "*.html" 44 | ], 45 | "extends": [ 46 | "plugin:@angular-eslint/template/recommended" 47 | ], 48 | "rules": {} 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Changes 2 | 3 | Please describe both what is changing and why this is important. Include: 4 | 5 | - Endpoints added, deleted, deprecated, or changed 6 | - Classes and methods added, deleted, deprecated, or changed 7 | - Screenshots of new or changed UI, if applicable 8 | - A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released) 9 | - Any alternative designs or approaches considered 10 | 11 | ### References 12 | 13 | Please include relevant links supporting this change such as a: 14 | 15 | - support ticket 16 | - community post 17 | - StackOverflow post 18 | - support forum thread 19 | 20 | ### Checklist 21 | 22 | - [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 23 | - [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) 24 | - [ ] All code quality tools/guidelines in the [CONTRIBUTING documentation](https://github.com/auth0/open-source-template/blob/master/CONTRIBUTING.md) have been run/followed 25 | - [ ] All relevant assets have been compiled as directed in the [CONTRIBUTING documentation](https://github.com/auth0/open-source-template/blob/master/CONTRIBUTING.md), if applicable 26 | -------------------------------------------------------------------------------- /.github/actions/npm-publish/action.yml: -------------------------------------------------------------------------------- 1 | name: Publish release to npm 2 | 3 | inputs: 4 | node-version: 5 | required: true 6 | npm-token: 7 | required: true 8 | version: 9 | required: true 10 | require-build: 11 | default: true 12 | release-directory: 13 | default: './' 14 | 15 | runs: 16 | using: composite 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup Node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ inputs.node-version }} 26 | cache: 'npm' 27 | registry-url: 'https://registry.npmjs.org' 28 | 29 | - name: Install dependencies 30 | shell: bash 31 | run: npm ci --include=dev 32 | 33 | - name: Build package 34 | if: inputs.require-build == 'true' 35 | shell: bash 36 | run: npm run build 37 | 38 | - name: Publish release to NPM 39 | shell: bash 40 | working-directory: ${{ inputs.release-directory }} 41 | run: | 42 | if [[ "${VERSION}" == *"beta"* ]]; then 43 | TAG="beta" 44 | elif [[ "${VERSION}" == *"alpha"* ]]; then 45 | TAG="alpha" 46 | else 47 | TAG="latest" 48 | fi 49 | npm publish --provenance --tag $TAG 50 | env: 51 | NODE_AUTH_TOKEN: ${{ inputs.npm-token }} 52 | VERSION: ${{ inputs.version }} 53 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from 'tsd'; 2 | import { JwtHelperService } from './index'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 5 | interface Token {} 6 | 7 | const service = new JwtHelperService(); 8 | 9 | expectType>(service.decodeToken()); 10 | expectType(service.decodeToken('')); 11 | expectType(service.decodeToken('TEST_TOKEN')); 12 | expectType>(service.decodeToken(Promise.resolve(''))); 13 | 14 | expectType>(service.isTokenExpired()); 15 | expectType(service.isTokenExpired('')); 16 | expectType(service.isTokenExpired('TEST_TOKEN')); 17 | expectType>(service.isTokenExpired(Promise.resolve(''))); 18 | 19 | expectType(service.isTokenExpired('', 0)); 20 | expectType(service.isTokenExpired('TEST_TOKEN', 0)); 21 | expectType(service.isTokenExpired(null, 0)); 22 | expectType>(service.isTokenExpired(undefined, 0)); 23 | 24 | expectType>(service.getTokenExpirationDate()); 25 | expectType(service.getTokenExpirationDate('')); 26 | expectType(service.getTokenExpirationDate('TEST_TOKEN')); 27 | expectType>(service.getTokenExpirationDate(Promise.resolve(''))); 28 | -------------------------------------------------------------------------------- /.github/actions/get-release-notes/action.yml: -------------------------------------------------------------------------------- 1 | name: Return the release notes extracted from the body of the PR associated with the release. 2 | 3 | # 4 | # Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | inputs: 9 | version: 10 | required: true 11 | repo_name: 12 | required: false 13 | repo_owner: 14 | required: true 15 | token: 16 | required: true 17 | 18 | outputs: 19 | release-notes: 20 | value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} 21 | 22 | runs: 23 | using: composite 24 | 25 | steps: 26 | - uses: actions/github-script@v7 27 | id: get_release_notes 28 | with: 29 | result-encoding: string 30 | script: | 31 | const { data: pulls } = await github.rest.pulls.list({ 32 | owner: process.env.REPO_OWNER, 33 | repo: process.env.REPO_NAME, 34 | state: 'all', 35 | head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, 36 | }); 37 | core.setOutput('RELEASE_NOTES', pulls[0].body); 38 | env: 39 | GITHUB_TOKEN: ${{ inputs.token }} 40 | REPO_OWNER: ${{ inputs.repo_owner }} 41 | REPO_NAME: ${{ inputs.repo_name }} 42 | VERSION: ${{ inputs.version }} 43 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/lib/angular-jwt.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NgModule, 3 | ModuleWithProviders, 4 | Optional, 5 | SkipSelf, 6 | Provider, 7 | } from '@angular/core'; 8 | import { HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http'; 9 | import { JwtInterceptor } from './jwt.interceptor'; 10 | import { JWT_OPTIONS } from './jwtoptions.token'; 11 | import { JwtHelperService } from './jwthelper.service'; 12 | 13 | export interface JwtConfig { 14 | tokenGetter?: ( 15 | request?: HttpRequest 16 | ) => string | null | Promise; 17 | headerName?: string; 18 | authScheme?: string | ((request?: HttpRequest) => string); 19 | allowedDomains?: Array; 20 | disallowedRoutes?: Array; 21 | throwNoTokenError?: boolean; 22 | skipWhenExpired?: boolean; 23 | } 24 | 25 | export interface JwtModuleOptions { 26 | jwtOptionsProvider?: Provider; 27 | config?: JwtConfig; 28 | } 29 | 30 | @NgModule() 31 | export class JwtModule { 32 | constructor(@Optional() @SkipSelf() parentModule: JwtModule) { 33 | if (parentModule) { 34 | throw new Error( 35 | `JwtModule is already loaded. It should only be imported in your application's main module.` 36 | ); 37 | } 38 | } 39 | static forRoot(options: JwtModuleOptions): ModuleWithProviders { 40 | return { 41 | ngModule: JwtModule, 42 | providers: [ 43 | { 44 | provide: HTTP_INTERCEPTORS, 45 | useClass: JwtInterceptor, 46 | multi: true, 47 | }, 48 | options.jwtOptionsProvider || { 49 | provide: JWT_OPTIONS, 50 | useValue: options.config, 51 | }, 52 | JwtHelperService, 53 | ], 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature Request.yml: -------------------------------------------------------------------------------- 1 | name: 🧩 Feature request 2 | description: Suggest an idea or a feature for this library 3 | labels: ["feature request"] 4 | 5 | body: 6 | - type: checkboxes 7 | id: checklist 8 | attributes: 9 | label: Checklist 10 | options: 11 | - label: I have looked into the [Readme](https://github.com/auth0/angular2-jwt#readme) and [Examples](https://github.com/auth0/angular2-jwt/blob/main/EXAMPLES.md), and have not found a suitable solution or answer. 12 | required: true 13 | - label: I have looked into the [API documentation](https://github.com/auth0/angular2-jwt/blob/main/API.md) and have not found a suitable solution or answer. 14 | required: true 15 | - label: I have searched the [issues](https://github.com/auth0/angular2-jwt/issues) and have not found a suitable solution or answer. 16 | required: true 17 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 18 | required: true 19 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 20 | required: true 21 | 22 | - type: textarea 23 | id: description 24 | attributes: 25 | label: Describe the problem you'd like to have solved 26 | description: A clear and concise description of what the problem is. 27 | placeholder: I'm always frustrated when... 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: ideal-solution 33 | attributes: 34 | label: Describe the ideal solution 35 | description: A clear and concise description of what you want to happen. 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: alternatives-and-workarounds 41 | attributes: 42 | label: Alternatives and current workarounds 43 | description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. 44 | validations: 45 | required: false 46 | 47 | - type: textarea 48 | id: additional-context 49 | attributes: 50 | label: Additional context 51 | description: Add any other context or screenshots about the feature request here. 52 | validations: 53 | required: false 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@auth0/angular-jwt", 3 | "version": "5.2.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build angular-jwt --configuration production", 8 | "postbuild": "cp ./README.md ./CHANGELOG.md ./LICENSE ./dist/angular-jwt/", 9 | "test": "ng test", 10 | "test:ci": "npm run test:angular-jwt && npm run test:angular2-jwt", 11 | "test:angular-jwt": "ng test angular-jwt --no-watch --no-progress --browsers=ChromeHeadlessCI --code-coverage", 12 | "test:angular2-jwt": "ng test angular2-jwt --no-watch --no-progress --browsers=ChromeHeadlessCI", 13 | "test:types": "tsd projects/angular-jwt/src", 14 | "lint": "ng lint", 15 | "e2e": "ng e2e", 16 | "release": "npm publish dist/angular-jwt/" 17 | }, 18 | "private": false, 19 | "dependencies": { 20 | "@angular/common": "^14.3.0", 21 | "@angular/compiler": "^14.3.0", 22 | "@angular/core": "^14.3.0", 23 | "@angular/platform-browser": "^14.3.0", 24 | "@angular/platform-browser-dynamic": "^14.3.0", 25 | "core-js": "^3.2.1", 26 | "rxjs": "~6.5.3", 27 | "tslib": "^2.4.0", 28 | "zone.js": "~0.11.4" 29 | }, 30 | "devDependencies": { 31 | "@angular-devkit/build-angular": "^14.2.13", 32 | "@angular-eslint/builder": "14.4.0", 33 | "@angular-eslint/eslint-plugin": "14.4.0", 34 | "@angular-eslint/eslint-plugin-template": "14.4.0", 35 | "@angular-eslint/schematics": "14.4.0", 36 | "@angular-eslint/template-parser": "14.4.0", 37 | "@angular/cli": "^14.2.13", 38 | "@angular/compiler-cli": "^14.3.0", 39 | "@angular/language-service": "^14.3.0", 40 | "@types/jasmine": "~3.6.0", 41 | "@types/jasminewd2": "~2.0.3", 42 | "@types/node": "^12.11.1", 43 | "@typescript-eslint/eslint-plugin": "5.27.1", 44 | "@typescript-eslint/parser": "5.27.1", 45 | "eslint": "^8.17.0", 46 | "jasmine-core": "~3.6.0", 47 | "jasmine-spec-reporter": "~5.0.0", 48 | "karma": "~6.4.1", 49 | "karma-chrome-launcher": "~3.1.0", 50 | "karma-coverage": "^2.2.1", 51 | "karma-jasmine": "~4.0.0", 52 | "karma-jasmine-html-reporter": "^1.5.0", 53 | "ng-packagr": "^14.2.2", 54 | "protractor": "~7.0.0", 55 | "ts-node": "~8.4.1", 56 | "tsd": "^0.25.0", 57 | "typescript": "~4.6.4" 58 | }, 59 | "np": { 60 | "contents": "dist/angular-jwt" 61 | }, 62 | "publishConfig": { 63 | "access": "public" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags.ts'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # Examples using angular2-jwt 2 | 3 | - [Using a Custom Options Factory Function](#using-a-custom-options-factory-function) 4 | - [Configuration for Ionic 2+](#configuration-for-ionic-2) 5 | 6 | ## Using a Custom Options Factory Function 7 | 8 | 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`). 9 | 10 | Import the `JWT_OPTIONS` `InjectionToken` so that you can instruct it to use your custom factory function. 11 | 12 | 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. 13 | 14 | ```ts 15 | import { JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt'; 16 | import { TokenService } from './app.tokenservice'; 17 | 18 | // ... 19 | 20 | export function jwtOptionsFactory(tokenService) { 21 | return { 22 | tokenGetter: () => { 23 | return tokenService.getAsyncToken(); 24 | }, 25 | allowedDomains: ["example.com"] 26 | } 27 | } 28 | 29 | // ... 30 | 31 | @NgModule({ 32 | // ... 33 | imports: [ 34 | JwtModule.forRoot({ 35 | jwtOptionsProvider: { 36 | provide: JWT_OPTIONS, 37 | useFactory: jwtOptionsFactory, 38 | deps: [TokenService] 39 | } 40 | }) 41 | ], 42 | providers: [TokenService] 43 | }) 44 | ``` 45 | 46 | **Note:**: If a `jwtOptionsFactory` is defined, then `config` is ignored. _Both configuration alternatives can't be defined at the same time_. 47 | 48 | ## Configuration for Ionic 2+ 49 | 50 | The custom factory function approach described above can be used to get a token asynchronously with Ionic's `Storage`. 51 | 52 | ```ts 53 | import { JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt'; 54 | import { Storage } from '@ionic/storage'; 55 | 56 | export function jwtOptionsFactory(storage) { 57 | return { 58 | tokenGetter: () => { 59 | return storage.get('access_token'); 60 | }, 61 | allowedDomains: ["example.com"] 62 | } 63 | } 64 | 65 | // ... 66 | 67 | @NgModule({ 68 | // ... 69 | imports: [ 70 | JwtModule.forRoot({ 71 | jwtOptionsProvider: { 72 | provide: JWT_OPTIONS, 73 | useFactory: jwtOptionsFactory, 74 | deps: [Storage] 75 | } 76 | }) 77 | ] 78 | }) 79 | ``` 80 | 81 | **Note:**: If a `jwtOptionsFactory` is defined, then `config` is ignored. _Both configuration alternatives can't be defined at the same time_. 82 | -------------------------------------------------------------------------------- /.github/workflows/npm-release.yml: -------------------------------------------------------------------------------- 1 | name: Create npm and GitHub Release 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | node-version: 7 | required: true 8 | type: string 9 | require-build: 10 | default: true 11 | type: string 12 | release-directory: 13 | default: './' 14 | type: string 15 | secrets: 16 | github-token: 17 | required: true 18 | npm-token: 19 | required: true 20 | 21 | jobs: 22 | release: 23 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) 24 | runs-on: ubuntu-latest 25 | environment: release 26 | 27 | steps: 28 | # Checkout the code 29 | - uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 32 | 33 | # Get the version from the branch name 34 | - id: get_version 35 | uses: ./get-version 36 | 37 | # Get the prerelease flag from the branch name 38 | - id: get_prerelease 39 | uses: ./get-prerelease 40 | with: 41 | version: ${{ steps.get_version.outputs.version }} 42 | 43 | # Get the release notes 44 | - id: get_release_notes 45 | uses: ./get-release-notes 46 | with: 47 | token: ${{ secrets.github-token }} 48 | version: ${{ steps.get_version.outputs.version }} 49 | repo_owner: ${{ github.repository_owner }} 50 | repo_name: ${{ github.event.repository.name }} 51 | 52 | # Check if the tag already exists 53 | - id: tag_exists 54 | uses: ./tag-exists 55 | with: 56 | tag: ${{ steps.get_version.outputs.version }} 57 | token: ${{ secrets.github-token }} 58 | 59 | # If the tag already exists, exit with an error 60 | - if: steps.tag_exists.outputs.exists == 'true' 61 | run: exit 1 62 | 63 | # Publish the release to our package manager 64 | - uses: ./npm-publish 65 | with: 66 | node-version: ${{ inputs.node-version }} 67 | require-build: ${{ inputs.require-build }} 68 | version: ${{ steps.get_version.outputs.version }} 69 | npm-token: ${{ secrets.npm-token }} 70 | release-directory: ${{ inputs.release-directory }} 71 | 72 | # Create a release for the tag 73 | - uses: ./release-create 74 | with: 75 | token: ${{ secrets.github-token }} 76 | name: ${{ steps.get_version.outputs.version }} 77 | body: ${{ steps.get_release_notes.outputs.release-notes }} 78 | tag: ${{ steps.get_version.outputs.version }} 79 | commit: ${{ github.sha }} 80 | prerelease: ${{ steps.get_prerelease.outputs.prerelease }} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug Report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Report a bug 2 | description: Have you found a bug or issue? Create a bug report for this library 3 | labels: ["bug"] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. 10 | 11 | - type: checkboxes 12 | id: checklist 13 | attributes: 14 | label: Checklist 15 | options: 16 | - label: I have looked into the [Readme](https://github.com/auth0/angular2-jwt#readme) and [Examples](https://github.com/auth0/angular2-jwt/blob/main/EXAMPLES.md), and have not found a suitable solution or answer. 17 | required: true 18 | - label: I have looked into the [API documentation](https://github.com/auth0/angular2-jwt/blob/main/API.md) and have not found a suitable solution or answer. 19 | required: true 20 | - label: I have searched the [issues](https://github.com/auth0/angular2-jwt/issues) and have not found a suitable solution or answer. 21 | required: true 22 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 23 | required: true 24 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 25 | required: true 26 | 27 | - type: textarea 28 | id: description 29 | attributes: 30 | label: Description 31 | description: Provide a clear and concise description of the issue, including what you expected to happen. 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: reproduction 37 | attributes: 38 | label: Reproduction 39 | description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. 40 | placeholder: | 41 | 1. Step 1... 42 | 2. Step 2... 43 | 3. ... 44 | validations: 45 | required: true 46 | 47 | - type: textarea 48 | id: additional-context 49 | attributes: 50 | label: Additional context 51 | description: Other libraries that might be involved, or any other relevant information you think would be useful. 52 | validations: 53 | required: false 54 | 55 | - type: input 56 | id: environment-version 57 | attributes: 58 | label: angular-jwt version 59 | validations: 60 | required: true 61 | 62 | - type: input 63 | id: environment-angular-version 64 | attributes: 65 | label: Angular version 66 | validations: 67 | required: true 68 | 69 | - type: dropdown 70 | id: environment-browser 71 | attributes: 72 | label: Which browsers have you tested in? 73 | multiple: true 74 | options: 75 | - Chrome 76 | - Edge 77 | - Safari 78 | - Firefox 79 | - Opera 80 | - IE 81 | - Other 82 | validations: 83 | required: true 84 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/lib/jwt.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { 3 | HttpRequest, 4 | HttpHandler, 5 | HttpEvent, 6 | HttpInterceptor, 7 | } from '@angular/common/http'; 8 | import { DOCUMENT } from '@angular/common'; 9 | import { JwtHelperService } from './jwthelper.service'; 10 | import { JWT_OPTIONS } from './jwtoptions.token'; 11 | 12 | import { map, mergeMap } from 'rxjs/operators'; 13 | import { defer, from, Observable, of } from 'rxjs'; 14 | 15 | const fromPromiseOrValue = (input: T | Promise) => { 16 | if (input instanceof Promise) { 17 | return defer(() => input); 18 | } 19 | return of(input); 20 | }; 21 | @Injectable() 22 | export class JwtInterceptor implements HttpInterceptor { 23 | tokenGetter: ( 24 | request?: HttpRequest 25 | ) => string | null | Promise; 26 | headerName: string; 27 | authScheme: string | ((request?: HttpRequest) => string); 28 | allowedDomains: Array; 29 | disallowedRoutes: Array; 30 | throwNoTokenError: boolean; 31 | skipWhenExpired: boolean; 32 | standardPorts: string[] = ['80', '443']; 33 | 34 | constructor( 35 | @Inject(JWT_OPTIONS) config: any, 36 | public jwtHelper: JwtHelperService, 37 | @Inject(DOCUMENT) private document: Document 38 | ) { 39 | this.tokenGetter = config.tokenGetter; 40 | this.headerName = config.headerName || 'Authorization'; 41 | this.authScheme = 42 | config.authScheme || config.authScheme === '' 43 | ? config.authScheme 44 | : 'Bearer '; 45 | this.allowedDomains = config.allowedDomains || []; 46 | this.disallowedRoutes = config.disallowedRoutes || []; 47 | this.throwNoTokenError = config.throwNoTokenError || false; 48 | this.skipWhenExpired = config.skipWhenExpired; 49 | } 50 | 51 | isAllowedDomain(request: HttpRequest): boolean { 52 | const requestUrl: URL = new URL(request.url, this.document.location.origin); 53 | 54 | // If the host equals the current window origin, 55 | // the domain is allowed by default 56 | if (requestUrl.host === this.document.location.host) { 57 | return true; 58 | } 59 | 60 | // If not the current domain, check the allowed list 61 | const hostName = `${requestUrl.hostname}${ 62 | requestUrl.port && !this.standardPorts.includes(requestUrl.port) 63 | ? ':' + requestUrl.port 64 | : '' 65 | }`; 66 | 67 | return ( 68 | this.allowedDomains.findIndex((domain) => 69 | typeof domain === 'string' 70 | ? domain === hostName 71 | : domain instanceof RegExp 72 | ? domain.test(hostName) 73 | : false 74 | ) > -1 75 | ); 76 | } 77 | 78 | isDisallowedRoute(request: HttpRequest): boolean { 79 | const requestedUrl: URL = new URL( 80 | request.url, 81 | this.document.location.origin 82 | ); 83 | 84 | return ( 85 | this.disallowedRoutes.findIndex((route: string | RegExp) => { 86 | if (typeof route === 'string') { 87 | const parsedRoute: URL = new URL( 88 | route, 89 | this.document.location.origin 90 | ); 91 | return ( 92 | parsedRoute.hostname === requestedUrl.hostname && 93 | parsedRoute.pathname === requestedUrl.pathname 94 | ); 95 | } 96 | 97 | if (route instanceof RegExp) { 98 | return route.test(request.url); 99 | } 100 | 101 | return false; 102 | }) > -1 103 | ); 104 | } 105 | 106 | handleInterception( 107 | token: string | null, 108 | request: HttpRequest, 109 | next: HttpHandler 110 | ) { 111 | const authScheme = this.jwtHelper.getAuthScheme(this.authScheme, request); 112 | 113 | if (!token && this.throwNoTokenError) { 114 | throw new Error('Could not get token from tokenGetter function.'); 115 | } 116 | 117 | let tokenIsExpired = of(false); 118 | 119 | if (this.skipWhenExpired) { 120 | tokenIsExpired = token ? fromPromiseOrValue(this.jwtHelper.isTokenExpired(token)) : of(true); 121 | } 122 | 123 | if (token) { 124 | return tokenIsExpired.pipe( 125 | map((isExpired) => 126 | isExpired && this.skipWhenExpired 127 | ? request.clone() 128 | : request.clone({ 129 | setHeaders: { 130 | [this.headerName]: `${authScheme}${token}`, 131 | }, 132 | }) 133 | ), 134 | mergeMap((innerRequest) => next.handle(innerRequest)) 135 | ); 136 | } 137 | 138 | return next.handle(request); 139 | } 140 | 141 | intercept( 142 | request: HttpRequest, 143 | next: HttpHandler 144 | ): Observable> { 145 | if (!this.isAllowedDomain(request) || this.isDisallowedRoute(request)) { 146 | return next.handle(request); 147 | } 148 | const token = this.tokenGetter(request); 149 | 150 | return fromPromiseOrValue(token).pipe( 151 | mergeMap((asyncToken: string | null) => { 152 | return this.handleInterception(asyncToken, request, next); 153 | }) 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular2-jwt": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/angular2-jwt", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "src/tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "src/styles.scss" 31 | ], 32 | "scripts": [], 33 | "vendorChunk": true, 34 | "extractLicenses": false, 35 | "buildOptimizer": false, 36 | "sourceMap": true, 37 | "optimization": false, 38 | "namedChunks": true 39 | }, 40 | "configurations": { 41 | "production": { 42 | "fileReplacements": [ 43 | { 44 | "replace": "src/environments/environment.ts", 45 | "with": "src/environments/environment.prod.ts" 46 | } 47 | ], 48 | "optimization": true, 49 | "outputHashing": "all", 50 | "sourceMap": false, 51 | "namedChunks": false, 52 | "extractLicenses": true, 53 | "vendorChunk": false, 54 | "buildOptimizer": true, 55 | "budgets": [ 56 | { 57 | "type": "initial", 58 | "maximumWarning": "2mb", 59 | "maximumError": "5mb" 60 | }, 61 | { 62 | "type": "anyComponentStyle", 63 | "maximumWarning": "6kb" 64 | } 65 | ] 66 | } 67 | }, 68 | "defaultConfiguration": "" 69 | }, 70 | "serve": { 71 | "builder": "@angular-devkit/build-angular:dev-server", 72 | "options": { 73 | "browserTarget": "angular2-jwt:build" 74 | }, 75 | "configurations": { 76 | "production": { 77 | "browserTarget": "angular2-jwt:build:production" 78 | } 79 | } 80 | }, 81 | "extract-i18n": { 82 | "builder": "@angular-devkit/build-angular:extract-i18n", 83 | "options": { 84 | "browserTarget": "angular2-jwt:build" 85 | } 86 | }, 87 | "test": { 88 | "builder": "@angular-devkit/build-angular:karma", 89 | "options": { 90 | "main": "src/test.ts", 91 | "polyfills": "src/polyfills.ts", 92 | "tsConfig": "src/tsconfig.spec.json", 93 | "karmaConfig": "src/karma.conf.js", 94 | "styles": [ 95 | "src/styles.scss" 96 | ], 97 | "scripts": [], 98 | "assets": [ 99 | "src/favicon.ico", 100 | "src/assets" 101 | ] 102 | } 103 | } 104 | } 105 | }, 106 | "angular2-jwt-e2e": { 107 | "root": "e2e/", 108 | "projectType": "application", 109 | "prefix": "", 110 | "architect": { 111 | "e2e": { 112 | "builder": "@angular-devkit/build-angular:protractor", 113 | "options": { 114 | "protractorConfig": "e2e/protractor.conf.js", 115 | "devServerTarget": "angular2-jwt:serve" 116 | }, 117 | "configurations": { 118 | "production": { 119 | "devServerTarget": "angular2-jwt:serve:production" 120 | } 121 | } 122 | } 123 | } 124 | }, 125 | "angular-jwt": { 126 | "root": "projects/angular-jwt", 127 | "sourceRoot": "projects/angular-jwt/src", 128 | "projectType": "library", 129 | "prefix": "", 130 | "architect": { 131 | "build": { 132 | "builder": "@angular-devkit/build-angular:ng-packagr", 133 | "options": { 134 | "tsConfig": "projects/angular-jwt/tsconfig.lib.json", 135 | "project": "projects/angular-jwt/ng-package.json" 136 | }, 137 | "configurations": { 138 | "production": { 139 | "tsConfig": "projects/angular-jwt/tsconfig.lib.prod.json" 140 | } 141 | } 142 | }, 143 | "test": { 144 | "builder": "@angular-devkit/build-angular:karma", 145 | "options": { 146 | "main": "projects/angular-jwt/src/test.ts", 147 | "tsConfig": "projects/angular-jwt/tsconfig.spec.json", 148 | "karmaConfig": "projects/angular-jwt/karma.conf.js" 149 | } 150 | }, 151 | "lint": { 152 | "builder": "@angular-eslint/builder:lint", 153 | "options": { 154 | "lintFilePatterns": [ 155 | "projects/angular-jwt/**/*.ts", 156 | "projects/angular-jwt/**/*.html" 157 | ] 158 | } 159 | } 160 | } 161 | } 162 | }, 163 | "cli": { 164 | "schematicCollections": [ 165 | "@angular-eslint/schematics" 166 | ] 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/lib/jwthelper.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpRequest } from '@angular/common/http'; 2 | /* eslint-disable no-bitwise */ 3 | 4 | import { Injectable, Inject } from '@angular/core'; 5 | import { JWT_OPTIONS } from './jwtoptions.token'; 6 | 7 | @Injectable() 8 | export class JwtHelperService { 9 | tokenGetter: () => string | Promise; 10 | 11 | constructor(@Inject(JWT_OPTIONS) config: any = null) { 12 | this.tokenGetter = (config && config.tokenGetter) || function () {}; 13 | } 14 | 15 | public urlBase64Decode(str: string): string { 16 | let output = str.replace(/-/g, '+').replace(/_/g, '/'); 17 | switch (output.length % 4) { 18 | case 0: { 19 | break; 20 | } 21 | case 2: { 22 | output += '=='; 23 | break; 24 | } 25 | case 3: { 26 | output += '='; 27 | break; 28 | } 29 | default: { 30 | throw new Error('Illegal base64url string!'); 31 | } 32 | } 33 | return this.b64DecodeUnicode(output); 34 | } 35 | 36 | // credits for decoder goes to https://github.com/atk 37 | private b64decode(str: string): string { 38 | const chars = 39 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 40 | let output = ''; 41 | 42 | str = String(str).replace(/=+$/, ''); 43 | 44 | if (str.length % 4 === 1) { 45 | throw new Error( 46 | `'atob' failed: The string to be decoded is not correctly encoded.` 47 | ); 48 | } 49 | 50 | for ( 51 | // initialize result and counters 52 | let bc = 0, bs: any, buffer: any, idx = 0; 53 | // get next character 54 | (buffer = str.charAt(idx++)); 55 | // character found in table? initialize bit storage and add its ascii value; 56 | ~buffer && 57 | ((bs = bc % 4 ? bs * 64 + buffer : buffer), 58 | // and if not first of each 4 characters, 59 | // convert the first 8 bits to one ascii character 60 | bc++ % 4) 61 | ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6)))) 62 | : 0 63 | ) { 64 | // try to find character in table (0-63, not found => -1) 65 | buffer = chars.indexOf(buffer); 66 | } 67 | return output; 68 | } 69 | 70 | private b64DecodeUnicode(str: any) { 71 | return decodeURIComponent( 72 | Array.prototype.map 73 | .call(this.b64decode(str), (c: any) => { 74 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 75 | }) 76 | .join('') 77 | ); 78 | } 79 | 80 | public decodeToken(token: string): T | null; 81 | public decodeToken(token: Promise): Promise; 82 | public decodeToken(): null | T | Promise; 83 | public decodeToken(token: string | Promise = this.tokenGetter()): null | T | Promise { 84 | if (token instanceof Promise) { 85 | return token.then(t => this._decodeToken(t)); 86 | } 87 | 88 | return this._decodeToken(token); 89 | } 90 | 91 | private _decodeToken(token: string): null | T { 92 | if (!token || token === '') { 93 | return null; 94 | } 95 | 96 | const parts = token.split('.'); 97 | 98 | if (parts.length !== 3) { 99 | throw new Error( 100 | `The inspected token doesn't appear to be a JWT. Check to make sure it has three parts and see https://jwt.io for more.` 101 | ); 102 | } 103 | 104 | const decoded = this.urlBase64Decode(parts[1]); 105 | if (!decoded) { 106 | throw new Error('Cannot decode the token.'); 107 | } 108 | 109 | return JSON.parse(decoded); 110 | } 111 | 112 | public getTokenExpirationDate(token: string): Date | null; 113 | public getTokenExpirationDate(token: Promise): Promise; 114 | public getTokenExpirationDate(): null | Date | Promise; 115 | public getTokenExpirationDate( 116 | token: string | Promise = this.tokenGetter() 117 | ): Date | null | Promise { 118 | if (token instanceof Promise) { 119 | return token.then(t => this._getTokenExpirationDate(t)); 120 | } 121 | 122 | return this._getTokenExpirationDate(token); 123 | } 124 | 125 | private _getTokenExpirationDate(token: string): Date | null { 126 | let decoded: any; 127 | decoded = this.decodeToken(token); 128 | 129 | if (!decoded || !decoded.hasOwnProperty('exp')) { 130 | return null; 131 | } 132 | 133 | const date = new Date(0); 134 | date.setUTCSeconds(decoded.exp); 135 | 136 | return date; 137 | } 138 | 139 | public isTokenExpired(token?: undefined, offsetSeconds?: number): boolean | Promise; 140 | public isTokenExpired(token: string | null, offsetSeconds?: number): boolean; 141 | public isTokenExpired(token: Promise, offsetSeconds?: number): Promise; 142 | public isTokenExpired( 143 | token: undefined | null | string | Promise = this.tokenGetter(), 144 | offsetSeconds?: number 145 | ): boolean | Promise { 146 | if (token instanceof Promise) { 147 | return token.then(t => this._isTokenExpired(t, offsetSeconds)); 148 | } 149 | 150 | return this._isTokenExpired(token, offsetSeconds); 151 | } 152 | 153 | private _isTokenExpired( 154 | token: string | null, 155 | offsetSeconds?: number 156 | ): boolean { 157 | if (!token || token === '') { 158 | return true; 159 | } 160 | const date = this.getTokenExpirationDate(token); 161 | offsetSeconds = offsetSeconds || 0; 162 | 163 | if (date === null) { 164 | return false; 165 | } 166 | 167 | return !(date.valueOf() > new Date().valueOf() + offsetSeconds * 1000); 168 | } 169 | 170 | public getAuthScheme( 171 | authScheme: Function | string | undefined, 172 | request: HttpRequest 173 | ): string | undefined { 174 | if (typeof authScheme === 'function') { 175 | return authScheme(request); 176 | } 177 | 178 | return authScheme; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v5.2.0](https://github.com/auth0/angular2-jwt/tree/v5.2.0) (2023-10-31) 4 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v5.1.2...v5.2.0) 5 | 6 | **Changed** 7 | - Drop support for Angular 13 and below [\#777](https://github.com/auth0/angular2-jwt/pull/777) ([frederikprijck](https://github.com/frederikprijck)) 8 | 9 | ## [v5.1.2](https://github.com/auth0/angular2-jwt/tree/v5.1.2) (2022-12-20) 10 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v5.1.1...v5.1.2) 11 | 12 | **Fixed** 13 | - Restore pre 5.1.1 behavior and fix types [\#759](https://github.com/auth0/angular2-jwt/pull/759) ([frederikprijck](https://github.com/frederikprijck)) 14 | 15 | ## [v5.1.1](https://github.com/auth0/angular2-jwt/tree/v5.1.1) (2022-12-15) 16 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v5.1.0...v5.1.1) 17 | 18 | **Fixed** 19 | - Support promise based tokenGetter in JwtHelperService [\#748](https://github.com/auth0/angular2-jwt/pull/748) ([frederikprijck](https://github.com/frederikprijck)) 20 | 21 | ## Version [5.1.0](https://github.com/auth0/angular2-jwt/tags/v5.1.0) 22 | 23 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v5.0.2..v5.1.0) 24 | 25 | **Changed** 26 | 27 | - Compile using Ivy partial mode [#735](https://github.com/auth0/angular2-jwt/pull/735) ([frederikprijck](https://github.com/frederikprijck)) 28 | 29 | Note: This release drops support for Angular <12 as [those versions are no longer supported by Google themselves](https://angular.io/guide/releases#actively-supported-versions). [[Read more ...](https://github.com/auth0/angular2-jwt/issues/712#issuecomment-1265009015)] 30 | 31 | ## Version [5.0.2](https://github.com/auth0/angular2-jwt/tags/v5.0.2) 32 | 33 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v5.0.1..v5.0.2) 34 | 35 | - Update `decodeToken` helper type definition to accept a generic. 36 | 37 | ## Version [5.0.1](https://github.com/auth0/angular2-jwt/tags/v5.0.1) 38 | 39 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v5.0.0..v5.0.1) 40 | 41 | - Remove dependency on the window object for SSR support 42 | 43 | ## Version [5.0.0](https://github.com/auth0/angular2-jwt/tags/v5.0.0) 44 | 45 | **Warning: this version has some breaking changes concerning the allowed domains and dissalowed routes!** 46 | 47 | - Replace `whitelistedDomains` to `allowedDomains` [#668](https://github.com/auth0/angular2-jwt/pull/668) 48 | - Replace `blacklistedRoutes` to `disallowedRoutes` [#668](https://github.com/auth0/angular2-jwt/pull/668) 49 | - Removed the url dependency, as this is a Node module in the CommonJS format, and the Angular 10 CLI throws warnings when using dependencies in the CommonJS format. We're using the default URL interface, https://developer.mozilla.org/en-US/docs/Web/API/URL [#666](https://github.com/auth0/angular2-jwt/pull/666) 50 | 51 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v4.2.0..v5.0.0) 52 | 53 | ## Version [4.2.0](https://github.com/auth0/angular2-jwt/tags/v4.2.0) 54 | 55 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v4.1.2..v4.2.0) 56 | 57 | - Allow the authScheme config parameter to be a getter function [#659](https://github.com/auth0/angular2-jwt/pull/659) 58 | 59 | ## Version [4.1.2](https://github.com/auth0/angular2-jwt/tags/v4.1.2) (2020-05-16) 60 | 61 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v4.1.1..v4.1.2) 62 | 63 | - Support domains with a port other than the default HTTP ports (HTTP: 80, HTTPS: 443) 64 | [#656](https://github.com/auth0/angular2-jwt/pull/656) 65 | 66 | ## Version [4.1.1](https://github.com/auth0/angular2-jwt/tags/v4.1.1) (2020-05-15) 67 | 68 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/v4.1.0..v4.1.1) 69 | 70 | - Something went wrong pulishing `v4.1.0`, this version fixes that. 71 | 72 | ## Version [4.1.0](https://github.com/auth0/angular2-jwt/tags/v4.1.0) (2020-05-15) 73 | 74 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/4.0.0..v4.1.0) 75 | 76 | - Use blacklist domains regardless of their protocol [#644](https://github.com/auth0/angular2-jwt/pull/644) 77 | - Pass the HttpRequest to the tokenGetter [#649](https://github.com/auth0/angular2-jwt/pull/649) 78 | 79 | ## Version [4.0.0](https://github.com/auth0/angular2-jwt/tags/v4.0.0) (2020-02-07) 80 | 81 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/3.0.1..4.0.0) 82 | 83 | From [\#622](https://github.com/auth0/angular2-jwt/pull/622) [avatsaev](https://github.com/avatsaev): 84 | 85 | - Angular 9 compatibility 86 | - Angular Ivy compatibility 87 | 88 | ## Version [3.0.1](https://github.com/auth0/angular2-jwt/tags/v3.0.1) (2019-10-28) 89 | 90 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/3.0.0..3.0.1) 91 | 92 | - Be sure to handle `undefined` tokens [\#626](https://github.com/auth0/angular2-jwt/pull/626) [@RobertRad](https://github.com/RobertRad) 93 | - docs: corrected/rephrased a sentence in README.md [\#625](https://github.com/auth0/angular2-jwt/pull/625) [@noopur-tiwari](https://github.com/noopur-tiwari) 94 | 95 | ## Version [3.0.0](https://github.com/auth0/angular2-jwt/releases/tag/3.0.0) (2019-07-16) 96 | 97 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/2.1.2..3.0.0) 98 | 99 | - **Breaking change** `isTokenExpired` now returns `false` if no expiry date is found inside the token. This is a change to align with the [JWT spec](https://tools.ietf.org/html/rfc7519#section-4.1.4), but may break applications that rely on the previous behavior. [#562](https://github.com/auth0/angular2-jwt/pull/562) [@atom-morgan](https://github.com/atom-morgan) 100 | 101 | ## Version [2.1.2](https://github.com/auth0/angular2-jwt/releases/tag/2.1.2) (2019-07-15) 102 | 103 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/2.1.1..2.1.2) 104 | 105 | - Gracefully handle null/empty tokens [#586](https://github.com/auth0/angular2-jwt/pull/586) 106 | 107 | ## Version [2.1.1](https://github.com/auth0/angular2-jwt/releases/tag/2.1.1) (2019-07-01) 108 | 109 | [Full Changelog](https://github.com/auth0/angular2-jwt/compare/2.1.0...2.1.1) 110 | 111 | - Blacklist/Whitelist check fix [#538](https://github.com/auth0/angular2-jwt/pull/538) 112 | - Refactor deep rxjs imports and use named define [#608](https://github.com/auth0/angular2-jwt/pull/608) 113 | - fix(rxjs): remove imports from rxjs/internal [#542](https://github.com/auth0/angular2-jwt/pull/542) 114 | 115 | > Note: historical changelog information has not been recorded in this format. Please see the [releases page](https://github.com/auth0/angular2-jwt/releases) for information on previous releases. 116 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## API reference 2 | 3 | - [JwtModule configuration options](#jwtmodule-configuration-options) 4 | - [JwtHelperService](#jwthelperservice) 5 | 6 | ### `JwtModule` configuration options 7 | 8 | #### `tokenGetter: function(HttpRequest): string` 9 | 10 | 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. 11 | 12 | ```ts 13 | // ... 14 | JwtModule.forRoot({ 15 | config: { 16 | // ... 17 | tokenGetter: () => { 18 | return localStorage.getItem("access_token"); 19 | }, 20 | }, 21 | }); 22 | ``` 23 | 24 | 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. 25 | 26 | ```ts 27 | // ... 28 | JwtModule.forRoot({ 29 | config: { 30 | // ... 31 | tokenGetter: (request) => { 32 | if (request.url.includes("foo")) { 33 | return localStorage.getItem("access_token_foo"); 34 | } 35 | 36 | return localStorage.getItem("access_token"); 37 | }, 38 | }, 39 | }); 40 | ``` 41 | 42 | #### `allowedDomains: array` 43 | 44 | 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. 45 | 46 | List any domains you wish to allow authenticated requests to be sent to by specifying them in the `allowedDomains` array. **Note that standard http port 80 and https port 443 requests don't require a port to be specified. A port is only required in the allowed domains host name if you are authenticating against a non-standard port e.g. localhost:3001** 47 | 48 | ```ts 49 | // ... 50 | JwtModule.forRoot({ 51 | config: { 52 | // ... 53 | allowedDomains: ["localhost:3001", "foo.com", "bar.com"], 54 | }, 55 | }); 56 | ``` 57 | 58 | #### `disallowedRoutes: array` 59 | 60 | If you do not want to replace the authorization headers for specific routes, list them here. This can be useful if your 61 | initial auth route(s) are on an allowed domain and take basic auth headers. These routes need to be prefixed with the correct protocol (`http://`, `https://`). If you want to add a route to the list of disallowed routes regardless of the protocol, you can prefix it with `//`. 62 | 63 | ```ts 64 | // ... 65 | JwtModule.forRoot({ 66 | config: { 67 | // ... 68 | disallowedRoutes: [ 69 | "http://localhost:3001/auth/", 70 | "https://foo.com/bar/", 71 | "//foo.com/bar/baz", 72 | /localhost:3001\/foo\/far.*/, 73 | ], // strings and regular expressions 74 | }, 75 | }); 76 | ``` 77 | 78 | **Note:** If requests are sent to the same domain that is serving your Angular application, you do not need to add that domain to the `allowedDomains` array. However, this is only the case if you don't specify the domain in the `Http` request. 79 | 80 | For example, the following request assumes that the domain is the same as the one serving your app. It doesn't need to be allowed in this case. 81 | 82 | ```ts 83 | this.http.get('/api/things') 84 | .subscribe(...) 85 | ``` 86 | 87 | However, if you are serving your API at the same domain as that which is serving your Angular app **and** you are specifying that domain in `Http` requests, then it **does** need to be explicitely allowed. 88 | 89 | ```ts 90 | // Both the Angular app and the API are served at 91 | // localhost:4200 but because that domain is specified 92 | // in the request, it must be allowed 93 | this.http.get('http://localhost:4200/api/things') 94 | .subscribe(...) 95 | ``` 96 | 97 | #### `headerName: string` 98 | 99 | The default header name is `Authorization`. This can be changed by specifying a custom `headerName` which is to be a string value. 100 | 101 | ```ts 102 | // ... 103 | JwtModule.forRoot({ 104 | config: { 105 | // ... 106 | headerName: "Your Header Name", 107 | }, 108 | }); 109 | ``` 110 | 111 | #### `authScheme: string | function(HttpRequest): string` 112 | 113 | The default authorization scheme is `Bearer` followed by a single space. This can be changed by specifying a custom `authScheme`. You can pass a string which will prefix the token for each request. 114 | 115 | ```ts 116 | // ... 117 | JwtModule.forRoot({ 118 | config: { 119 | // ... 120 | authScheme: "Basic ", 121 | }, 122 | }); 123 | ``` 124 | 125 | If you want to change the auth scheme dynamically, or based on the request, you can configure a getter function which returns a string. 126 | 127 | ```ts 128 | // ... 129 | JwtModule.forRoot({ 130 | config: { 131 | // ... 132 | authScheme: (request) => { 133 | if (request.url.includes("foo")) { 134 | return "Basic "; 135 | } 136 | 137 | return "Bearer "; 138 | }, 139 | }, 140 | }); 141 | ``` 142 | 143 | #### `throwNoTokenError: boolean` 144 | 145 | Setting `throwNoTokenError` to `true` will result in an error being thrown if a token cannot be retrieved with the `tokenGetter` function. Defaults to `false`. 146 | 147 | ```ts 148 | // ... 149 | JwtModule.forRoot({ 150 | config: { 151 | // ... 152 | throwNoTokenError: true, 153 | }, 154 | }); 155 | ``` 156 | 157 | #### `skipWhenExpired: boolean` 158 | 159 | By default, the user's JWT will be sent in `HttpClient` requests even if it is expired. You may choose to not allow the token to be sent if it is expired by setting `skipWhenExpired` to true. 160 | 161 | ```ts 162 | // ... 163 | JwtModule.forRoot({ 164 | config: { 165 | // ... 166 | skipWhenExpired: true, 167 | }, 168 | }); 169 | ``` 170 | 171 | ### `JwtHelperService` 172 | 173 | This service contains helper functions: 174 | 175 | #### isTokenExpired (old tokenNotExpired function) 176 | 177 | ```ts 178 | import { JwtHelperService } from '@auth0/angular-jwt'; 179 | // ... 180 | constructor(public jwtHelper: JwtHelperService) {} 181 | 182 | ngOnInit() { 183 | console.log(this.jwtHelper.isTokenExpired()); // true or false 184 | } 185 | ``` 186 | 187 | #### getTokenExpirationDate 188 | 189 | ```ts 190 | import { JwtHelperService } from '@auth0/angular-jwt'; 191 | // ... 192 | constructor(public jwtHelper: JwtHelperService) {} 193 | 194 | ngOnInit() { 195 | console.log(this.jwtHelper.getTokenExpirationDate()); // date 196 | } 197 | ``` 198 | 199 | #### decodeToken 200 | 201 | ```ts 202 | import { JwtHelperService } from '@auth0/angular-jwt'; 203 | // ... 204 | constructor(public jwtHelper: JwtHelperService) {} 205 | 206 | ngOnInit() { 207 | console.log(this.jwtHelper.decodeToken(token)); // token 208 | } 209 | ``` 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Helper library for handling JWTs in Angular applications](https://cdn.auth0.com/website/sdks/banners/angular-jwt-banner.png) 2 | 3 | ![Release](https://img.shields.io/github/v/release/auth0/angular2-jwt) 4 | [![codecov](https://codecov.io/gh/auth0/angular2-jwt/branch/main/graph/badge.svg?token=wnauXldcdE)](https://codecov.io/gh/auth0/angular2-jwt) 5 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/auth0/angular2-jwt) 6 | ![Downloads](https://img.shields.io/npm/dw/@auth0/angular-jwt) 7 | [![License](https://img.shields.io/:license-MIT-blue.svg?style=flat)](https://opensource.org/licenses/MIT) 8 | [![CircleCI](https://img.shields.io/circleci/build/github/auth0/angular2-jwt)](https://circleci.com/gh/auth0/angular2-jwt) 9 | 10 | :books: [Documentation](#documentation) - :rocket: [Getting Started](#getting-started) - :computer: [API Reference](#api-reference) - :speech_balloon: [Feedback](#feedback) 11 | 12 | ## Documentation 13 | 14 | - [Examples](https://github.com/auth0/angular2-jwt/blob/main/EXAMPLES.md) - code samples for common angular-jwt authentication scenario's. 15 | - [Docs site](https://www.auth0.com/docs) - explore our docs site and learn more about Auth0. 16 | 17 | This library provides an `HttpInterceptor` which automatically attaches a [JSON Web Token](https://jwt.io) to `HttpClient` requests. 18 | 19 | This library does not have any functionality for (or opinion about) implementing user authentication and retrieving JWTs to begin with. Those details will vary depending on your setup, but in most cases, you will use a regular HTTP request to authenticate your users and then save their JWTs in local storage or in a cookie if successful. 20 | 21 | ## Getting started 22 | ### Requirements 23 | This project only supports the [actively supported versions of Angular as stated in the Angular documentation](https://angular.io/guide/releases#actively-supported-versions). Whilst other versions might be compatible they are not actively supported 24 | 25 | ### Installation 26 | 27 | ```bash 28 | # installation with npm 29 | npm install @auth0/angular-jwt 30 | 31 | # installation with yarn 32 | yarn add @auth0/angular-jwt 33 | ``` 34 | 35 | ## Configure the SDK 36 | 37 | Import the `JwtModule` module and add it to your imports list. Call the `forRoot` method and provide a `tokenGetter` function. You must also add any domains to the `allowedDomains`, that you want to make requests to by specifying an `allowedDomains` array. 38 | 39 | Be sure to import the `HttpClientModule` as well. 40 | 41 | ```ts 42 | import { JwtModule } from "@auth0/angular-jwt"; 43 | import { HttpClientModule } from "@angular/common/http"; 44 | 45 | export function tokenGetter() { 46 | return localStorage.getItem("access_token"); 47 | } 48 | 49 | @NgModule({ 50 | bootstrap: [AppComponent], 51 | imports: [ 52 | // ... 53 | HttpClientModule, 54 | JwtModule.forRoot({ 55 | config: { 56 | tokenGetter: tokenGetter, 57 | allowedDomains: ["example.com"], 58 | disallowedRoutes: ["http://example.com/examplebadroute/"], 59 | }, 60 | }), 61 | ], 62 | }) 63 | export class AppModule {} 64 | ``` 65 | 66 | Any requests sent using Angular's `HttpClient` will automatically have a token attached as an `Authorization` header. 67 | 68 | ```ts 69 | import { HttpClient } from "@angular/common/http"; 70 | 71 | export class AppComponent { 72 | constructor(public http: HttpClient) {} 73 | 74 | ping() { 75 | this.http.get("http://example.com/api/things").subscribe( 76 | (data) => console.log(data), 77 | (err) => console.log(err) 78 | ); 79 | } 80 | } 81 | ``` 82 | 83 | ## Using with Standalone Components 84 | If you are using `bootstrapApplication` to bootstrap your application using a standalone component, you will need a slightly different way to integrate our SDK: 85 | 86 | ```ts 87 | import { JwtModule } from "@auth0/angular-jwt"; 88 | import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; 89 | 90 | export function tokenGetter() { 91 | return localStorage.getItem("access_token"); 92 | } 93 | 94 | bootstrapApplication(AppComponent, { 95 | providers: [ 96 | // ... 97 | importProvidersFrom( 98 | JwtModule.forRoot({ 99 | config: { 100 | tokenGetter: tokenGetter, 101 | allowedDomains: ["example.com"], 102 | disallowedRoutes: ["http://example.com/examplebadroute/"], 103 | }, 104 | }), 105 | ), 106 | provideHttpClient( 107 | withInterceptorsFromDi() 108 | ), 109 | ], 110 | }); 111 | ``` 112 | As you can see, the differences are that: 113 | - The SDK's module is included trough `importProvidersFrom`. 114 | - In order to use the SDK's interceptor, `provideHttpClient` needs to be called with `withInterceptorsFromDi`. 115 | 116 | 117 | ## API reference 118 | Read [our API reference](https://github.com/auth0/angular2-jwt/blob/main/API.md) to get a better understanding on how to use this SDK. 119 | 120 | ## Feedback 121 | 122 | ### Contributing 123 | 124 | We appreciate feedback and contribution to this repo! Before you get started, please see the following: 125 | 126 | - [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 127 | - [Auth0's code of conduct guidelines](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) 128 | - [This repo's contribution guide](https://github.com/auth0/angular2-jwt/blob/main/CONTRIBUTING.md) 129 | ### Raise an issue 130 | 131 | To provide feedback or report a bug, please [raise an issue on our issue tracker](https://github.com/auth0/angular2-jwt/issues). 132 | 133 | ### Vulnerability Reporting 134 | 135 | Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. 136 | 137 | --- 138 | 139 |

140 | 141 | 142 | 143 | Auth0 Logo 144 | 145 |

146 |

Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0?

147 |

148 | This project is licensed under the MIT license. See the LICENSE file for more info.

-------------------------------------------------------------------------------- /src/app/services/example-http.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { fakeAsync, flush, TestBed } from '@angular/core/testing'; 2 | import { ExampleHttpService } from './example-http.service'; 3 | import { 4 | HttpClientTestingModule, 5 | HttpTestingController, 6 | } from '@angular/common/http/testing'; 7 | import { JwtModule } from 'angular-jwt'; 8 | 9 | export function tokenGetter() { 10 | return 'TEST_TOKEN'; 11 | } 12 | 13 | export function tokenGetterWithRequest(request) { 14 | if (request.url.includes('1')) { 15 | return 'TEST_TOKEN_1'; 16 | } 17 | 18 | if (request.url.includes('2')) { 19 | return 'TEST_TOKEN_2'; 20 | } 21 | 22 | return 'TEST_TOKEN'; 23 | } 24 | 25 | export function tokenGetterWithPromise() { 26 | return Promise.resolve('TEST_TOKEN'); 27 | } 28 | 29 | describe('Example HttpService: with promise based tokken getter', () => { 30 | let service: ExampleHttpService; 31 | let httpMock: HttpTestingController; 32 | 33 | const validRoutes = [ 34 | `/assets/example-resource.json`, 35 | `http://allowed.com/api/`, 36 | `http://allowed.com/api/test`, 37 | `http://allowed.com:443/api/test`, 38 | `http://allowed-regex.com/api/`, 39 | `https://allowed-regex.com/api/`, 40 | `http://localhost:3000`, 41 | `http://localhost:3000/api`, 42 | ]; 43 | const invalidRoutes = [ 44 | `http://allowed.com/api/disallowed`, 45 | `http://allowed.com/api/disallowed-protocol`, 46 | `http://allowed.com:80/api/disallowed-protocol`, 47 | `http://allowed.com/api/disallowed-regex`, 48 | `http://allowed-regex.com/api/disallowed-regex`, 49 | `http://foo.com/bar`, 50 | 'http://localhost/api', 51 | 'http://localhost:4000/api', 52 | ]; 53 | 54 | beforeEach(() => { 55 | TestBed.configureTestingModule({ 56 | imports: [ 57 | HttpClientTestingModule, 58 | JwtModule.forRoot({ 59 | config: { 60 | tokenGetter: tokenGetterWithPromise, 61 | allowedDomains: ['allowed.com', /allowed-regex*/, 'localhost:3000'], 62 | disallowedRoutes: [ 63 | 'http://allowed.com/api/disallowed-protocol', 64 | '//allowed.com/api/disallowed', 65 | /disallowed-regex*/, 66 | ], 67 | }, 68 | }), 69 | ], 70 | }); 71 | service = TestBed.get(ExampleHttpService); 72 | httpMock = TestBed.get(HttpTestingController); 73 | }); 74 | 75 | it('should add Authorisation header', () => { 76 | expect(service).toBeTruthy(); 77 | }); 78 | 79 | validRoutes.forEach((route) => 80 | it(`should set the correct auth token for a allowed domain: ${route}`, fakeAsync(() => { 81 | service.testRequest(route).subscribe((response) => { 82 | expect(response).toBeTruthy(); 83 | }); 84 | 85 | flush(); 86 | const httpRequest = httpMock.expectOne(route); 87 | 88 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 89 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 90 | `Bearer TEST_TOKEN` 91 | ); 92 | })) 93 | ); 94 | 95 | invalidRoutes.forEach((route) => 96 | it(`should not set the auth token for a disallowed route: ${route}`, fakeAsync(() => { 97 | service.testRequest(route).subscribe((response) => { 98 | expect(response).toBeTruthy(); 99 | }); 100 | 101 | flush(); 102 | const httpRequest = httpMock.expectOne(route); 103 | expect(httpRequest.request.headers.has('Authorization')).toEqual(false); 104 | }) 105 | )); 106 | }); 107 | 108 | describe('Example HttpService: with simple tokken getter', () => { 109 | let service: ExampleHttpService; 110 | let httpMock: HttpTestingController; 111 | 112 | const validRoutes = [ 113 | `/assets/example-resource.json`, 114 | `http://allowed.com/api/`, 115 | `http://allowed.com/api/test`, 116 | `http://allowed.com:443/api/test`, 117 | `http://allowed-regex.com/api/`, 118 | `https://allowed-regex.com/api/`, 119 | `http://localhost:3000`, 120 | `http://localhost:3000/api`, 121 | ]; 122 | const invalidRoutes = [ 123 | `http://allowed.com/api/disallowed`, 124 | `http://allowed.com/api/disallowed-protocol`, 125 | `http://allowed.com:80/api/disallowed-protocol`, 126 | `http://allowed.com/api/disallowed-regex`, 127 | `http://allowed-regex.com/api/disallowed-regex`, 128 | `http://foo.com/bar`, 129 | 'http://localhost/api', 130 | 'http://localhost:4000/api', 131 | ]; 132 | 133 | beforeEach(() => { 134 | TestBed.configureTestingModule({ 135 | imports: [ 136 | HttpClientTestingModule, 137 | JwtModule.forRoot({ 138 | config: { 139 | tokenGetter: tokenGetter, 140 | allowedDomains: ['allowed.com', /allowed-regex*/, 'localhost:3000'], 141 | disallowedRoutes: [ 142 | 'http://allowed.com/api/disallowed-protocol', 143 | '//allowed.com/api/disallowed', 144 | /disallowed-regex*/, 145 | ], 146 | }, 147 | }), 148 | ], 149 | }); 150 | service = TestBed.get(ExampleHttpService); 151 | httpMock = TestBed.get(HttpTestingController); 152 | }); 153 | 154 | it('should add Authorisation header', () => { 155 | expect(service).toBeTruthy(); 156 | }); 157 | 158 | validRoutes.forEach((route) => 159 | it(`should set the correct auth token for a allowed domain: ${route}`, () => { 160 | service.testRequest(route).subscribe((response) => { 161 | expect(response).toBeTruthy(); 162 | }); 163 | 164 | const httpRequest = httpMock.expectOne(route); 165 | 166 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 167 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 168 | `Bearer ${tokenGetter()}` 169 | ); 170 | }) 171 | ); 172 | 173 | invalidRoutes.forEach((route) => 174 | it(`should not set the auth token for a disallowed route: ${route}`, () => { 175 | service.testRequest(route).subscribe((response) => { 176 | expect(response).toBeTruthy(); 177 | }); 178 | 179 | const httpRequest = httpMock.expectOne(route); 180 | expect(httpRequest.request.headers.has('Authorization')).toEqual(false); 181 | }) 182 | ); 183 | }); 184 | 185 | describe('Example HttpService: with request based tokken getter', () => { 186 | let service: ExampleHttpService; 187 | let httpMock: HttpTestingController; 188 | 189 | const routes = [ 190 | `http://example-1.com/api/`, 191 | `http://example-2.com/api/`, 192 | `http://example-3.com/api/`, 193 | ]; 194 | 195 | beforeEach(() => { 196 | TestBed.configureTestingModule({ 197 | imports: [ 198 | HttpClientTestingModule, 199 | JwtModule.forRoot({ 200 | config: { 201 | tokenGetter: tokenGetterWithRequest, 202 | allowedDomains: ['example-1.com', 'example-2.com', 'example-3.com'], 203 | }, 204 | }), 205 | ], 206 | }); 207 | service = TestBed.get(ExampleHttpService); 208 | httpMock = TestBed.get(HttpTestingController); 209 | }); 210 | 211 | it('should add Authorisation header', () => { 212 | expect(service).toBeTruthy(); 213 | }); 214 | 215 | routes.forEach((route) => 216 | it(`should set the correct auth token for a domain: ${route}`, () => { 217 | service.testRequest(route).subscribe((response) => { 218 | expect(response).toBeTruthy(); 219 | }); 220 | 221 | const httpRequest = httpMock.expectOne(route); 222 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 223 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 224 | `Bearer ${tokenGetterWithRequest({ url: route })}` 225 | ); 226 | }) 227 | ); 228 | }); 229 | 230 | const authSchemes = [ 231 | [undefined, 'Bearer '], 232 | ['Basic ', 'Basic '], 233 | [() => 'Basic ', 'Basic '], 234 | ]; 235 | 236 | authSchemes.forEach((scheme) => { 237 | let service: ExampleHttpService; 238 | let httpMock: HttpTestingController; 239 | 240 | describe(`Example HttpService: with ${ 241 | typeof scheme[0] === 'function' 242 | ? 'an authscheme getter function' 243 | : 'a simple authscheme getter' 244 | }`, () => { 245 | beforeEach(() => { 246 | TestBed.configureTestingModule({ 247 | imports: [ 248 | HttpClientTestingModule, 249 | JwtModule.forRoot({ 250 | config: { 251 | tokenGetter: tokenGetter, 252 | authScheme: scheme[0], 253 | allowedDomains: ['allowed.com'], 254 | }, 255 | }), 256 | ], 257 | }); 258 | service = TestBed.get(ExampleHttpService); 259 | httpMock = TestBed.get(HttpTestingController); 260 | }); 261 | 262 | it(`should set the correct auth scheme a request (${scheme[1]})`, () => { 263 | service.testRequest('http://allowed.com').subscribe((response) => { 264 | expect(response).toBeTruthy(); 265 | }); 266 | 267 | const httpRequest = httpMock.expectOne('http://allowed.com'); 268 | expect(httpRequest.request.headers.has('Authorization')).toEqual(true); 269 | expect(httpRequest.request.headers.get('Authorization')).toEqual( 270 | `${scheme[1]}${tokenGetter()}` 271 | ); 272 | }); 273 | }); 274 | }); 275 | -------------------------------------------------------------------------------- /projects/angular-jwt/src/lib/jwthelper.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { 3 | HttpClientTestingModule, 4 | } from '@angular/common/http/testing'; 5 | import { JwtModule, JwtHelperService } from 'angular-jwt'; 6 | 7 | describe('JwtHelperService: with simple based tokken getter', () => { 8 | let service: JwtHelperService; 9 | const tokenGetter = jasmine.createSpy('tokenGetter'); 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | HttpClientTestingModule, 15 | JwtModule.forRoot({ 16 | config: { 17 | tokenGetter: tokenGetter, 18 | allowedDomains: ['example-1.com', 'example-2.com', 'example-3.com'], 19 | }, 20 | }), 21 | ], 22 | }); 23 | service = TestBed.inject(JwtHelperService); 24 | 25 | tokenGetter.calls.reset(); 26 | }); 27 | 28 | describe('calling decodeToken', () => { 29 | it('should return null when tokenGetter returns null', () => { 30 | tokenGetter.and.returnValue(null); 31 | 32 | expect(service.decodeToken()).toBeNull(); 33 | }); 34 | 35 | it('should throw an error when token contains less than 2 dots', () => { 36 | tokenGetter.and.returnValue('a.b'); 37 | 38 | expect(() => service.decodeToken()).toThrow(); 39 | }); 40 | 41 | it('should throw an error when token contains more than 2 dots', () => { 42 | tokenGetter.and.returnValue('a.b.c.d'); 43 | 44 | expect(() => service.decodeToken()).toThrow(); 45 | }); 46 | 47 | it('should call the tokenGetter when no token passed', () => { 48 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 49 | 50 | const result: any = service.decodeToken(); 51 | 52 | expect(tokenGetter).toHaveBeenCalled(); 53 | expect(result.name).toBe('John Doe'); 54 | }); 55 | 56 | it('should call the tokenGetter when undefined is passed', () => { 57 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 58 | 59 | const result = service.decodeToken(undefined); 60 | 61 | expect(tokenGetter).toHaveBeenCalled(); 62 | expect(result.name).toBe('John Doe'); 63 | }); 64 | 65 | it('should not call the tokenGetter when token passed', () => { 66 | tokenGetter.and.returnValue(null); 67 | 68 | const result = service.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 69 | 70 | expect(tokenGetter).not.toHaveBeenCalled(); 71 | expect(result.name).toBe('John Doe'); 72 | }); 73 | 74 | it('should not call the tokenGetter when token passed as empty string', () => { 75 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 76 | 77 | const result = service.decodeToken(''); 78 | 79 | expect(tokenGetter).not.toHaveBeenCalled(); 80 | expect(result).toBeNull(); 81 | }); 82 | }); 83 | 84 | describe('calling getTokenExpirationDate', () => { 85 | it('should call the tokenGetter when no token passed', () => { 86 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 87 | 88 | const result = service.getTokenExpirationDate() as Date; 89 | 90 | expect(tokenGetter).toHaveBeenCalled(); 91 | expect(result.getFullYear()).toBe(2018); 92 | expect(result.getMonth() + 1).toBe(1); 93 | expect(result.getDate()).toBe(18); 94 | }); 95 | 96 | it('should call the tokenGetter when undefined is passed', () => { 97 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 98 | 99 | const result = service.getTokenExpirationDate(undefined) as Date; 100 | 101 | expect(tokenGetter).toHaveBeenCalled(); 102 | expect(result.getFullYear()).toBe(2018); 103 | expect(result.getMonth() + 1).toBe(1); 104 | expect(result.getDate()).toBe(18); 105 | }); 106 | 107 | it('should not call the tokenGetter when token passed', () => { 108 | tokenGetter.and.returnValue(null); 109 | 110 | const result = service.getTokenExpirationDate('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 111 | 112 | expect(tokenGetter).not.toHaveBeenCalled(); 113 | expect(result.getFullYear()).toBe(2018); 114 | expect(result.getMonth() + 1).toBe(1); 115 | expect(result.getDate()).toBe(18); 116 | }); 117 | 118 | it('should not call the tokenGetter when token passed as empty string', () => { 119 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 120 | 121 | const result = service.getTokenExpirationDate(''); 122 | 123 | expect(tokenGetter).not.toHaveBeenCalled(); 124 | expect(result).toBeNull(); 125 | }); 126 | }); 127 | 128 | describe('calling isTokenExpired', () => { 129 | it('should call the tokenGetter when no token passed', () => { 130 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 131 | 132 | const result = service.isTokenExpired() as Boolean; 133 | 134 | expect(tokenGetter).toHaveBeenCalled(); 135 | expect(result).toBeTrue(); 136 | }); 137 | 138 | it('should call the tokenGetter when undefined is passed', () => { 139 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 140 | 141 | const result = service.isTokenExpired(undefined); 142 | 143 | expect(tokenGetter).toHaveBeenCalled(); 144 | expect(result).toBeTrue(); 145 | }); 146 | 147 | it('should not call the tokenGetter when token passed', () => { 148 | tokenGetter.and.returnValue(null); 149 | 150 | const result = service.isTokenExpired('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 151 | 152 | expect(tokenGetter).not.toHaveBeenCalled(); 153 | expect(result).toBeTrue(); 154 | }); 155 | 156 | it('should not call the tokenGetter when token passed as empty string', () => { 157 | tokenGetter.and.returnValue('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 158 | 159 | const result = service.isTokenExpired(''); 160 | 161 | expect(tokenGetter).not.toHaveBeenCalled(); 162 | expect(result).toBeTrue(); 163 | }); 164 | }); 165 | 166 | }); 167 | 168 | describe('JwtHelperService: with a promise based tokken getter', () => { 169 | let service: JwtHelperService; 170 | const tokenGetter = jasmine.createSpy('tokenGetter'); 171 | 172 | beforeEach(() => { 173 | TestBed.configureTestingModule({ 174 | imports: [ 175 | HttpClientTestingModule, 176 | JwtModule.forRoot({ 177 | config: { 178 | tokenGetter: tokenGetter, 179 | allowedDomains: ['example-1.com', 'example-2.com', 'example-3.com'], 180 | }, 181 | }), 182 | ], 183 | }); 184 | service = TestBed.inject(JwtHelperService); 185 | tokenGetter.calls.reset(); 186 | }); 187 | 188 | describe('calling decodeToken', () => { 189 | it('should return null when tokenGetter returns null', async () => { 190 | tokenGetter.and.resolveTo(null); 191 | 192 | await expectAsync(service.decodeToken()).toBeResolvedTo(null); 193 | }); 194 | 195 | it('should throw an error when token contains less than 2 dots', async () => { 196 | tokenGetter.and.resolveTo('a.b'); 197 | 198 | await expectAsync(service.decodeToken()).toBeRejected(); 199 | }); 200 | 201 | it('should throw an error when token contains more than 2 dots', async () => { 202 | tokenGetter.and.resolveTo('a.b.c.d'); 203 | 204 | await expectAsync(service.decodeToken()).toBeRejected(); 205 | }); 206 | 207 | it('should return the token when tokenGetter returns a valid JWT', async () => { 208 | tokenGetter.and.resolveTo('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NjY2ODU4NjAsImV4cCI6MTY5ODIyMTg2MCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.lXrRPRZ8VNUpwBsT9fLPPO0p0BotQle4siItqg4LqLQ'); 209 | 210 | await expectAsync(service.decodeToken()).toBeResolvedTo(jasmine.anything()); 211 | }); 212 | 213 | it('should call the tokenGetter when no token passed', async () => { 214 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 215 | 216 | const result = await service.decodeToken(); 217 | 218 | expect(tokenGetter).toHaveBeenCalled(); 219 | expect(result.name).toBe('John Doe'); 220 | }); 221 | 222 | it('should call the tokenGetter when undefined is passed', async () => { 223 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 224 | 225 | const result = await service.decodeToken(undefined); 226 | 227 | expect(tokenGetter).toHaveBeenCalled(); 228 | expect(result.name).toBe('John Doe'); 229 | }); 230 | 231 | it('should not call the tokenGetter when token passed', () => { 232 | tokenGetter.and.resolveTo(null); 233 | 234 | const result = service.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 235 | 236 | expect(tokenGetter).not.toHaveBeenCalled(); 237 | expect(result.name).toBe('John Doe'); 238 | }); 239 | 240 | it('should not call the tokenGetter when token passed as empty string', () => { 241 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'); 242 | 243 | const result = service.decodeToken(''); 244 | 245 | expect(tokenGetter).not.toHaveBeenCalled(); 246 | expect(result).toBeNull(); 247 | }); 248 | }); 249 | 250 | describe('calling getTokenExpirationDate', () => { 251 | it('should call the tokenGetter when no token passed', async () => { 252 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 253 | 254 | const result = await service.getTokenExpirationDate(); 255 | 256 | expect(tokenGetter).toHaveBeenCalled(); 257 | expect(result.getFullYear()).toBe(2018); 258 | expect(result.getMonth() + 1).toBe(1); 259 | expect(result.getDate()).toBe(18); 260 | }); 261 | 262 | it('should call the tokenGetter when undefined is passed', async () => { 263 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 264 | 265 | const result = await service.getTokenExpirationDate(undefined); 266 | 267 | expect(tokenGetter).toHaveBeenCalled(); 268 | expect(result.getFullYear()).toBe(2018); 269 | expect(result.getMonth() + 1).toBe(1); 270 | expect(result.getDate()).toBe(18); 271 | }); 272 | 273 | it('should not call the tokenGetter when token passed', () => { 274 | tokenGetter.and.resolveTo(null); 275 | 276 | const result = service.getTokenExpirationDate('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 277 | 278 | expect(tokenGetter).not.toHaveBeenCalled(); 279 | expect(result.getFullYear()).toBe(2018); 280 | expect(result.getMonth() + 1).toBe(1); 281 | expect(result.getDate()).toBe(18); 282 | }); 283 | 284 | it('should not call the tokenGetter when token passed as empty string', () => { 285 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 286 | 287 | const result = service.getTokenExpirationDate(''); 288 | 289 | expect(tokenGetter).not.toHaveBeenCalled(); 290 | expect(result).toBeNull(); 291 | }); 292 | }); 293 | 294 | describe('calling isTokenExpired', () => { 295 | it('should call the tokenGetter when no token passed', async () => { 296 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 297 | 298 | const result = await service.isTokenExpired(); 299 | 300 | expect(tokenGetter).toHaveBeenCalled(); 301 | expect(result).toBeTrue(); 302 | }); 303 | 304 | it('should call the tokenGetter when undefined is passed', async () => { 305 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 306 | 307 | const result = await service.isTokenExpired(undefined); 308 | 309 | expect(tokenGetter).toHaveBeenCalled(); 310 | expect(result).toBeTrue(); 311 | }); 312 | 313 | it('should not call the tokenGetter when token passed', () => { 314 | tokenGetter.and.resolveTo(null); 315 | 316 | const result = service.isTokenExpired('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 317 | 318 | expect(tokenGetter).not.toHaveBeenCalled(); 319 | expect(result).toBeTrue(); 320 | }); 321 | 322 | it('should not call the tokenGetter when token passed as empty string', () => { 323 | tokenGetter.and.resolveTo('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-KQ'); 324 | 325 | const result = service.isTokenExpired(''); 326 | 327 | expect(tokenGetter).not.toHaveBeenCalled(); 328 | expect(result).toBeTrue(); 329 | }); 330 | }); 331 | }); 332 | 333 | --------------------------------------------------------------------------------