├── .editorconfig ├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support-request--general-question.md ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CREDITS.md ├── LICENSE ├── README.md ├── angular.json ├── commitlint.config.js ├── contributors.bat ├── contributors.md ├── docs-src ├── authsvr-auth0.md ├── authsvr-azure-ad.md ├── authsvr-idsvr.md ├── authsvr-keycloak.md ├── authsvr.md ├── callback-after-login.md ├── code-flow.bak.md ├── code-flow.md ├── configure-custom-oauthstorage.md ├── custom-date-time-provider.md ├── custom-query-parameter.md ├── discovery-document-validation.md ├── events.md ├── getting-started.md ├── hash-strategy.md ├── id-token-validation.md ├── implicit-flow-config-discovery.md ├── implicit-flow-config-no-discovery.md ├── implicit-flow.md ├── interceptors.md ├── password-flow.md ├── popup.md ├── preserving-state.md ├── server-side-rendering.md ├── session-checks.md ├── silent-refresh.md ├── skipping-login-form.md ├── summary.json ├── systemjs.md └── token-refresh.md ├── docs ├── additional-documentation │ ├── adapt-id_token-validation.html │ ├── authorization-servers.html │ ├── authorization-servers │ │ ├── auth0.html │ │ ├── azure-ad-(active-directory).html │ │ ├── using-identity-server.html │ │ └── using-keycloak.html │ ├── callback-after-login.html │ ├── code-flow-+-pcke.html │ ├── configure-custom-oauthstorage.html │ ├── configure-library-for-implicit-flow-without-discovery-document.html │ ├── configure │ │ └── -adapt-id_token-validation.html │ ├── custom-datetimeprovider.html │ ├── custom-query-parameters.html │ ├── events.html │ ├── getting-started.html │ ├── manually-skipping-login-form.html │ ├── original-config-api.html │ ├── popup-based-login.html │ ├── preserving-state-(like-the-requested-url).html │ ├── refreshing-a-token-(silent-refresh).html │ ├── refreshing-a-token.html │ ├── routing-with-the-hashstrategy.html │ ├── server-side-rendering.html │ ├── session-checks.html │ ├── silent-refresh.html │ ├── token-refresh.html │ ├── using-an-id-provider-that-fails-discovery-document-validation.html │ ├── using-implicit-flow.html │ ├── using-password-flow.html │ ├── using-systemjs.html │ └── working-with-httpinterceptors.html ├── changelog.html ├── classes │ ├── AbstractValidationHandler.html │ ├── AuthConfig.html │ ├── CryptoHandler.html │ ├── DateTimeProvider.html │ ├── HMAC.html │ ├── Hash.html │ ├── HashHandler.html │ ├── JwksValidationHandler.html │ ├── LoginOptions.html │ ├── NullValidationHandler.html │ ├── OAuthErrorEvent.html │ ├── OAuthEvent.html │ ├── OAuthInfoEvent.html │ ├── OAuthLogger.html │ ├── OAuthModuleConfig.html │ ├── OAuthNoopResourceServerErrorHandler.html │ ├── OAuthResourceServerConfig.html │ ├── OAuthResourceServerErrorHandler.html │ ├── OAuthStorage.html │ ├── OAuthSuccessEvent.html │ ├── ReceivedTokens.html │ ├── ValidationHandler.html │ └── WebHttpUrlEncodingCodec.html ├── coverage.html ├── dependencies.html ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── ionicons.eot │ ├── ionicons.svg │ ├── ionicons.ttf │ ├── ionicons.woff │ ├── ionicons.woff2 │ ├── roboto-v15-latin-300.eot │ ├── roboto-v15-latin-300.svg │ ├── roboto-v15-latin-300.ttf │ ├── roboto-v15-latin-300.woff │ ├── roboto-v15-latin-300.woff2 │ ├── roboto-v15-latin-700.eot │ ├── roboto-v15-latin-700.svg │ ├── roboto-v15-latin-700.ttf │ ├── roboto-v15-latin-700.woff │ ├── roboto-v15-latin-700.woff2 │ ├── roboto-v15-latin-italic.eot │ ├── roboto-v15-latin-italic.svg │ ├── roboto-v15-latin-italic.ttf │ ├── roboto-v15-latin-italic.woff │ ├── roboto-v15-latin-italic.woff2 │ ├── roboto-v15-latin-regular.eot │ ├── roboto-v15-latin-regular.svg │ ├── roboto-v15-latin-regular.ttf │ ├── roboto-v15-latin-regular.woff │ └── roboto-v15-latin-regular.woff2 ├── graph │ └── dependencies.svg ├── images │ ├── compodoc-vectorise-inverted.png │ ├── compodoc-vectorise-inverted.svg │ ├── compodoc-vectorise.png │ ├── compodoc-vectorise.svg │ ├── coverage-badge-documentation.svg │ └── favicon.ico ├── index.html ├── injectables │ ├── DefaultHashHandler.html │ ├── MemoryStorage.html │ ├── OAuthService.html │ ├── SystemDateTimeProvider.html │ └── UrlHelperService.html ├── interceptors │ └── DefaultOAuthInterceptor.html ├── interfaces │ ├── OidcDiscoveryDoc.html │ ├── ParsedIdToken.html │ ├── TokenResponse.html │ ├── UserInfo.html │ └── ValidationParams.html ├── js │ ├── compodoc.js │ ├── lazy-load-graphs.js │ ├── libs │ │ ├── EventDispatcher.js │ │ ├── bootstrap-native.js │ │ ├── clipboard.min.js │ │ ├── custom-elements-es5-adapter.js │ │ ├── custom-elements.min.js │ │ ├── d3.v3.min.js │ │ ├── deep-iterator.js │ │ ├── es6-shim.min.js │ │ ├── htmlparser.js │ │ ├── innersvg.js │ │ ├── lit-html.js │ │ ├── prism.js │ │ ├── promise.min.js │ │ ├── svg-pan-zoom.min.js │ │ ├── tablesort.min.js │ │ ├── tablesort.number.min.js │ │ ├── vis.min.js │ │ └── zepto.min.js │ ├── menu-wc.js │ ├── menu-wc_es5.js │ ├── menu.js │ ├── routes.js │ ├── search │ │ ├── lunr.min.js │ │ ├── search-lunr.js │ │ ├── search.js │ │ └── search_index.js │ ├── sourceCode.js │ ├── svg-pan-zoom.controls.js │ ├── tabs.js │ └── tree.js ├── license.html ├── miscellaneous │ ├── functions.html │ ├── typealiases.html │ └── variables.html ├── modules.html ├── modules │ └── OAuthModule.html ├── overview.html ├── properties.html └── styles │ ├── bootstrap-card.css │ ├── bootstrap.min.css │ ├── compodoc.css │ ├── dark.css │ ├── font-awesome.min.css │ ├── ionicons.min.css │ ├── laravel.css │ ├── material.css │ ├── original.css │ ├── postmark.css │ ├── prism.css │ ├── readthedocs.css │ ├── reset.css │ ├── stripe.css │ ├── style.css │ ├── tablesort.css │ └── vagrant.css ├── oidc.png ├── package-lock.json ├── package.json ├── projects ├── angular-oauth2-oidc-jwks │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ └── jwks-validation-handler.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── lib │ ├── .eslintrc.json │ ├── karma.conf.js │ ├── ng-package.json │ ├── ng-package.prod.json │ ├── package.json │ ├── src │ │ ├── .vscode │ │ │ └── settings.json │ │ ├── angular-oauth-oidc.module.ts │ │ ├── auth.config.ts │ │ ├── base64-helper.ts │ │ ├── date-time-provider.ts │ │ ├── deps.d.ts │ │ ├── encoder.ts │ │ ├── events.ts │ │ ├── factories.ts │ │ ├── interceptors │ │ │ ├── default-oauth.interceptor.ts │ │ │ └── resource-server-error-handler.ts │ │ ├── oauth-module.config.ts │ │ ├── oauth-service.ts │ │ ├── provider.ts │ │ ├── public_api.ts │ │ ├── token-validation │ │ │ ├── fast-sha256js.ts │ │ │ ├── hash-handler.ts │ │ │ ├── js-sha256.js │ │ │ ├── jwks-validation-handler.ts │ │ │ ├── null-validation-handler.ts │ │ │ └── validation-handler.ts │ │ ├── tokens.ts │ │ ├── types.ts │ │ └── url-helper.service.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── quickstart-demo │ ├── .browserslistrc │ ├── karma.conf.js │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ └── auth.config.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ └── test.ts │ ├── tsconfig.app.json │ └── tsconfig.spec.json ├── quickstart-standalone │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ └── auth.config.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ └── styles.css │ ├── tsconfig.app.json │ └── tsconfig.spec.json └── sample │ ├── karma.conf.js │ ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app.routes.ts │ │ ├── app.tokens.ts │ │ ├── auth-code-flow.config.ts │ │ ├── auth-no-discovery.config.ts │ │ ├── auth-password-flow.config.ts │ │ ├── auth.config.ts │ │ ├── auth.google.config.ts │ │ ├── entities │ │ │ └── flight.ts │ │ ├── flight-booking │ │ │ ├── alt-flight-card │ │ │ │ ├── alt-flight-card.component.html │ │ │ │ ├── alt-flight.card.component.ts │ │ │ │ └── flight-list.ts │ │ │ ├── flight-booking.component.html │ │ │ ├── flight-booking.component.ts │ │ │ ├── flight-booking.module.ts │ │ │ ├── flight-booking.routes.ts │ │ │ ├── flight-card │ │ │ │ ├── flight-card.component.html │ │ │ │ └── flight.card.component.ts │ │ │ ├── flight-edit │ │ │ │ └── flight-edit.component.ts │ │ │ ├── flight-search-reactive │ │ │ │ ├── flight-search-reactive.component.css │ │ │ │ ├── flight-search-reactive.component.html │ │ │ │ └── flight-search-reactive.component.ts │ │ │ ├── flight-search │ │ │ │ ├── flight-search.component.css │ │ │ │ ├── flight-search.component.html │ │ │ │ └── flight-search.component.ts │ │ │ ├── passenger-search │ │ │ │ └── passenger-search.component.ts │ │ │ └── services │ │ │ │ └── flight.service.ts │ │ ├── flight-history │ │ │ └── flight-history.component.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ └── home.component.ts │ │ ├── password-flow-login │ │ │ ├── password-flow-login.component.html │ │ │ └── password-flow-login.component.ts │ │ └── shared │ │ │ ├── auth │ │ │ └── auth.guard.ts │ │ │ ├── date │ │ │ ├── custom-date-time-provider.ts │ │ │ └── date.component.ts │ │ │ ├── pipes │ │ │ └── city.pipe.ts │ │ │ ├── preload │ │ │ └── custom-preloading.strategy.ts │ │ │ ├── shared.module.ts │ │ │ └── validation │ │ │ ├── async-city.validator.ts │ │ │ ├── city.validator.ts │ │ │ └── roundtrip.validator.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── flags.ts │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── silent-refresh.html │ ├── styles.css │ ├── test.ts │ └── typings.d.ts │ ├── tsconfig.app.json │ └── tsconfig.spec.json ├── tsconfig.json ├── tsconfig.npm.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://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 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/recommended", 14 | "plugin:@angular-eslint/recommended", 15 | "plugin:@angular-eslint/template/process-inline-templates" 16 | ], 17 | "rules": { 18 | "@angular-eslint/directive-selector": [ 19 | "error", 20 | { 21 | "type": "attribute", 22 | "prefix": "lib", 23 | "style": "camelCase" 24 | } 25 | ], 26 | "@angular-eslint/component-selector": [ 27 | "error", 28 | { 29 | "type": "element", 30 | "prefix": "lib", 31 | "style": "kebab-case" 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "files": [ 38 | "*.html" 39 | ], 40 | "extends": [ 41 | "plugin:@angular-eslint/template/recommended", 42 | "plugin:@angular-eslint/template/accessibility" 43 | ], 44 | "rules": {} 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Stackblitz example** 14 | Provide a minimal [stackblitz](https://stackblitz.com/) based example that shows the issue. For this, you can use the example application of this repo and the identity providers used here. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | 27 | **Desktop (please complete the following information):** 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request--general-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request/ general question 3 | about: Requesting help from the community 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Stackblitz example** 14 | Provide a minimal [stackblitz](https://stackblitz.com/) based example that shows the issue. For this, you can use the example application of this repo and the identity providers used here. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | 27 | **Desktop (please complete the following information):** 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.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 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.angular/cache 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:4200", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.enabledLanguageIds": [ 3 | "asciidoc", 4 | "c", 5 | "cpp", 6 | "csharp", 7 | "css", 8 | "go", 9 | "handlebars", 10 | "html", 11 | "jade", 12 | "javascript", 13 | "javascriptreact", 14 | "json", 15 | "latex", 16 | "less", 17 | "php", 18 | "plaintext", 19 | "pub", 20 | "python", 21 | "restructuredtext", 22 | "rust", 23 | "scss", 24 | "text", 25 | "typescriptreact", 26 | "yml" 27 | ], 28 | "spellright.language": [ 29 | "en" 30 | ], 31 | "spellright.documentTypes": [ 32 | "latex", 33 | "plaintext", 34 | "markdown" 35 | ], 36 | "files.exclude": { 37 | "**/.git": true, 38 | "**/.svn": true, 39 | "**/.hg": true, 40 | "**/CVS": true, 41 | "**/.DS_Store": true, 42 | "**/Thumbs.db": true 43 | }, 44 | "hide-files.files": [] 45 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build:sample", 9 | "problemMatcher": [ 10 | "$tsc" 11 | ], 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | This lib uses an inlined version of [js-sha256](https://www.npmjs.com/package/js-sha256) (MIT license) 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Manfred Steyer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * To check all configuration options, please visit 3 | * https://commitlint.js.org/#/reference-rules 4 | */ 5 | module.exports = { 6 | extends: ['@commitlint/config-conventional'], 7 | }; 8 | -------------------------------------------------------------------------------- /contributors.bat: -------------------------------------------------------------------------------- 1 | npx github-contributors-list --repo angular-oauth2-oidc --owner manfredsteyer 2 | -------------------------------------------------------------------------------- /docs-src/authsvr-auth0.md: -------------------------------------------------------------------------------- 1 | # Using Auth0 2 | 3 | To use this lib with Auth0, open your Auth0 account and configure: 4 | 5 | - An app 6 | - An API 7 | 8 | Configure the app to use ``refresh token rotation`` and the grant types ``authorization code`` and ``refresh token``. For grant types, see the advanced settings at the end of the settings page. 9 | 10 | ## Configuration 11 | 12 | Provide a configuration like this: 13 | 14 | ```typescript 15 | import { AuthConfig } from 'angular-oauth2-oidc'; 16 | 17 | export const authConfig: AuthConfig = { 18 | 19 | issuer: 'https://dev-g-61sdfs.eu.auth0.com/', 20 | 21 | // Your app's client id: 22 | clientId: 'opHt1Tkt9E9fVQTZPBVF1tHVhjrxvyVX', 23 | redirectUri: window.location.origin, 24 | 25 | scope: 'openid profile email offline_access', 26 | 27 | responseType: 'code', 28 | 29 | logoutUrl: 'https://dev-g-61sdfs.eu.auth0.com/v2/logout', 30 | 31 | customQueryParams: { 32 | // Your API's name 33 | audience: 'http://www.angular.at/api' 34 | }, 35 | }; 36 | ``` 37 | 38 | ## Getting, Using, and Refreshing a Token 39 | 40 | This should work as shown in the other examples in this documentation and in the readme file. 41 | 42 | ## Logging out 43 | 44 | Auth0's logout endpoint expects the parameters ``client_id`` and ``returnTo``: 45 | 46 | ```typescript 47 | this.oauthService.revokeTokenAndLogout({ 48 | client_id: this.oauthService.clientId, 49 | returnTo: this.oauthService.redirectUri 50 | }, true); 51 | ``` 52 | 53 | The optional 2nd parameter set to ``true`` ignores CORS issues with the logout endpoint. 54 | 55 | ## Example 56 | 57 | Please find a [demo](https://github.com/manfredsteyer/auth0-demo) for using Auth0 with angular-oauth2-oidc [here](https://github.com/manfredsteyer/auth0-demo). 58 | -------------------------------------------------------------------------------- /docs-src/authsvr-azure-ad.md: -------------------------------------------------------------------------------- 1 | # Using with Azure AD 2 | 3 | For using this library with **Azure Active Directory** (**Azure AD**), we recommend an additional look to this [blog post](https://dev.to/yuriburger/azure-active-directory-b2c-with-pkce-for-your-angular-app-1dcg) and the example linked at the end of this blog post. 4 | -------------------------------------------------------------------------------- /docs-src/authsvr-idsvr.md: -------------------------------------------------------------------------------- 1 | # Using Identity Server 2 | 3 | This lib should work as shown in all examples here with Identity Server. -------------------------------------------------------------------------------- /docs-src/authsvr-keycloak.md: -------------------------------------------------------------------------------- 1 | # Using Keycloak 2 | 3 | This lib should work as shown in all examples here with Keycloak. -------------------------------------------------------------------------------- /docs-src/authsvr.md: -------------------------------------------------------------------------------- 1 | # Support for Auth Servers 2 | 3 | As this lib follows the OAuth2 and OpenId Connect specs, it should work with all compliant authorizations servers. 4 | 5 | However, experience shows that some authorizations servers come with some special behavior or settings. Hence, we must respect this when using this lib. -------------------------------------------------------------------------------- /docs-src/callback-after-login.md: -------------------------------------------------------------------------------- 1 | # Callback after login 2 | 3 | There is a callback ``onTokenReceived``, that is called after a successful login. In this case, the lib received the access_token as 4 | well as the id_token, if it was requested. If there is an id_token, the lib validated it. 5 | 6 | ```TypeScript 7 | this.oauthService.tryLogin({ 8 | onTokenReceived: context => { 9 | // 10 | // Output just for purpose of demonstration 11 | // Don't try this at home ... ;-) 12 | // 13 | console.debug("logged in"); 14 | console.debug(context); 15 | } 16 | }); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs-src/code-flow.bak.md: -------------------------------------------------------------------------------- 1 | # Code Flow 2 | 3 | Since Version 8, this library also supports code flow and [PKCE](https://tools.ietf.org/html/rfc7636) to align with the current draft of the [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13) document. 4 | 5 | 6 | To configure your solution for code flow + PKCE you have to set the `responseType` to `code`: 7 | 8 | ```TypeScript 9 | 10 | import { AuthConfig } from 'angular-oauth2-oidc'; 11 | 12 | export const authCodeFlowConfig: AuthConfig = { 13 | // Url of the Identity Provider 14 | issuer: 'https://demo.identityserver.io', 15 | 16 | // URL of the SPA to redirect the user to after login 17 | redirectUri: window.location.origin + '/index.html', 18 | 19 | // The SPA's id. The SPA is registerd with this id at the auth-server 20 | // clientId: 'server.code', 21 | clientId: 'spa', 22 | 23 | // Just needed if your auth server demands a secret. In general, this 24 | // is a sign that the auth server is not configured with SPAs in mind 25 | // and it might not enforce further best practices vital for security 26 | // such applications. 27 | // dummyClientSecret: 'secret', 28 | 29 | responseType: 'code', 30 | 31 | // set the scope for the permissions the client should request 32 | // The first four are defined by OIDC. 33 | // Important: Request offline_access to get a refresh token 34 | // The api scope is a usecase specific one 35 | scope: 'openid profile email offline_access api', 36 | 37 | showDebugInformation: true, 38 | 39 | // Not recommented: 40 | // disablePKCI: true, 41 | }; 42 | ``` 43 | 44 | After this, you can initialize the code flow using: 45 | 46 | ```TypeScript 47 | 48 | this.oauthService.initCodeFlow(); 49 | ``` 50 | 51 | There is also a convenience method `initLoginFlow` which initializes either the code flow or the implicit flow depending on your configuration. 52 | 53 | ```TypeScript 54 | this.oauthService.initLoginFlow(); 55 | ``` 56 | 57 | Also -- as shown in the readme -- you have to execute the following code when bootstrapping to make the library to fetch the token: 58 | 59 | ```TypeScript 60 | this.oauthService.configure(authCodeFlowConfig); 61 | this.oauthService.tokenValidationHandler = new JwksValidationHandler(); 62 | this.oauthService.loadDiscoveryDocumentAndTryLogin(); 63 | ``` 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs-src/code-flow.md: -------------------------------------------------------------------------------- 1 | # Code Flow 2 | 3 | Since Version 8, this library also supports code flow and [PKCE](https://tools.ietf.org/html/rfc7636) to align with the current draft of the [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13) document. 4 | 5 | 6 | To configure your solution for code flow + PKCE you have to set the `responseType` to `code`: 7 | 8 | ```TypeScript 9 | import { AuthConfig } from 'angular-oauth2-oidc'; 10 | 11 | export const authCodeFlowConfig: AuthConfig = { 12 | // Url of the Identity Provider 13 | issuer: 'https://demo.identityserver.io', 14 | 15 | // URL of the SPA to redirect the user to after login 16 | redirectUri: window.location.origin + '/index.html', 17 | 18 | // The SPA's id. The SPA is registered with this id at the auth-server 19 | // clientId: 'server.code', 20 | clientId: 'spa', 21 | 22 | // Just needed if your auth server demands a secret. In general, this 23 | // is a sign that the auth server is not configured with SPAs in mind 24 | // and it might not enforce further best practices vital for security 25 | // such applications. 26 | // dummyClientSecret: 'secret', 27 | 28 | responseType: 'code', 29 | 30 | // set the scope for the permissions the client should request 31 | // The first four are defined by OIDC. 32 | // Important: Request offline_access to get a refresh token 33 | // The api scope is a usecase specific one 34 | scope: 'openid profile email offline_access api', 35 | 36 | showDebugInformation: true, 37 | 38 | // Not recommended: 39 | // disablePKCE: true, 40 | }; 41 | ``` 42 | 43 | After this, you can initialize the code flow using: 44 | 45 | ```TypeScript 46 | this.oauthService.initCodeFlow(); 47 | ``` 48 | 49 | There is also a convenience method `initLoginFlow` which initializes either the code flow or the implicit flow depending on your configuration. 50 | 51 | ```TypeScript 52 | this.oauthService.initLoginFlow(); 53 | ``` 54 | 55 | Also -- as shown in the readme -- you have to execute the following code when bootstrapping to make the library to fetch the token: 56 | 57 | ```TypeScript 58 | this.oauthService.configure(authCodeFlowConfig); 59 | this.oauthService.loadDiscoveryDocumentAndTryLogin(); 60 | ``` 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs-src/configure-custom-oauthstorage.md: -------------------------------------------------------------------------------- 1 | # Configure custom OAuthStorage 2 | 3 | This library uses `sessionStorage` as the default storage provider. You can customize this by using `localStorage` or your own storage solution. 4 | 5 | ## Using localStorage 6 | If you want to use `localStorage` instead of `sessionStorage`, you can add a provider to your AppModule. This works as follows: 7 | 8 | ```TypeScript 9 | import { HttpClientModule } from '@angular/common/http'; 10 | import { OAuthModule } from 'angular-oauth2-oidc'; 11 | // etc. 12 | 13 | // We need a factory, since localStorage is not available during AOT build time. 14 | export function storageFactory() : OAuthStorage { 15 | return localStorage 16 | } 17 | 18 | @NgModule({ 19 | imports: [ 20 | // etc. 21 | HttpClientModule, 22 | OAuthModule.forRoot() 23 | ], 24 | declarations: [ 25 | AppComponent, 26 | HomeComponent, 27 | // etc. 28 | ], 29 | bootstrap: [ 30 | AppComponent 31 | ], 32 | providers: [ 33 | { provide: OAuthStorage, useFactory: storageFactory } 34 | ] 35 | }) 36 | export class AppModule { 37 | } 38 | ``` 39 | 40 | ## Custom storage solution 41 | 42 | If you want to use a custom storage solution, you can extend the `OAuthStorage` class. Documentation can be found [here](../classes/OAuthStorage.html#info). Then add it as a provider, just like in the `localStorage` example above. -------------------------------------------------------------------------------- /docs-src/custom-date-time-provider.md: -------------------------------------------------------------------------------- 1 | # Custom DateTimeProvider 2 | 3 | If your Identity Provider's clock is not synchronized, the validation of the token could fail. 4 | If the deviation is only some seconds, you can use the `AuthConfig.clockSkewInSec` setting to allow a bigger time window deviation. 5 | 6 | However, you may need to adjust the base time, that is used for the token validation and make sure, that the `AuthConfig.clockSkewInSec` is still a small reasonable number, then you can implement a custom `DateTimeProvider`. 7 | 8 | To do so, create a new service that derives from `DateTimeProvider`: 9 | 10 | ```typescript 11 | export class MyCustomDateTimeProvider extends DateTimeProvider { 12 | now(): number { 13 | // Return your custom now. 14 | return Date.now(); 15 | } 16 | 17 | new(): Date { 18 | // Return your custom new Date(). 19 | return new Date(); 20 | } 21 | } 22 | ``` 23 | 24 | Then, override the provider via dependency injection in your application: 25 | 26 | ```typescript 27 | @NgModule({ 28 | imports: [ 29 | // etc. 30 | OAuthModule.forRoot() 31 | ], 32 | providers: [ 33 | { provide: DateTimeProvider, useClass: MyCustomDateTimeProvider } // <- add this 34 | ], 35 | declarations: [ 36 | AppComponent, 37 | // etc. 38 | ], 39 | bootstrap: [ 40 | AppComponent 41 | ] 42 | }) 43 | export class AppModule { 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /docs-src/custom-query-parameter.md: -------------------------------------------------------------------------------- 1 | # Custom Query Parameters 2 | 3 | You can set the property ``customQueryParams`` to a hash with custom parameter that are transmitted when starting implicit flow. 4 | 5 | ```TypeScript 6 | this.oauthService.customQueryParams = { 7 | 'tenant': '4711', 8 | 'otherParam': 'someValue' 9 | }; 10 | ``` 11 | -------------------------------------------------------------------------------- /docs-src/discovery-document-validation.md: -------------------------------------------------------------------------------- 1 | # Discovery Document Validation 2 | 3 | The configuration parameter `strictDiscoveryDocumentValidation` is set `true` by default. This ensures that all of the endpoints provided via the ID Provider discovery document share the same base URL as the `issuer` parameter. 4 | 5 | Several ID Providers (i.e. Google OpenID, WS02-IS, PingOne) provide different domains or path params for various endpoints in the discovery document. These providers may still adhere to the [OpenID Connect Provider Configuration specification](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse), but will fail to pass this library's discovery document validation. 6 | 7 | To use this library with an ID Provider that does not maintain a consistent base URL across the discovery document endpoints, set the `strictDiscoveryDocumentValidation` parameter to `false` in your configuration: 8 | 9 | ```TypeScript 10 | import { AuthConfig } from 'angular-oauth2-oidc'; 11 | 12 | export const authConfig: AuthConfig = { 13 | 14 | // Url of the Identity Provider 15 | issuer: 'https://steyer-identity-server.azurewebsites.net/identity', 16 | 17 | // URL of the SPA to redirect the user to after login 18 | redirectUri: window.location.origin + '/index.html', 19 | 20 | // The SPA's id. The SPA is registerd with this id at the auth-server 21 | clientId: 'spa-demo', 22 | 23 | // set the scope for the permissions the client should request 24 | // The first three are defined by OIDC. The 4th is a usecase-specific one 25 | scope: 'openid profile email voucher', 26 | 27 | // turn off validation that discovery document endpoints start with the issuer url defined above 28 | strictDiscoveryDocumentValidation: false 29 | } 30 | ``` -------------------------------------------------------------------------------- /docs-src/events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | The library informs you about its tasks and state using [events](https://manfredsteyer.github.io/angular-oauth2-oidc/docs/injectables/OAuthService.html#events). 4 | This is an `Observable` which publishes a stream of events as they occur in the service. 5 | You can log these events to the console for debugging information. 6 | 7 | A short snippet you could use: 8 | 9 | ```TypeScript 10 | this.oauthService.events.subscribe(e => console.log(e)); 11 | ``` 12 | 13 | Or a longer, more extensive version that logs them at different levels: 14 | 15 | ```TypeScript 16 | import { OAuthErrorEvent } from 'angular-oauth2-oidc'; 17 | 18 | // ... 19 | 20 | this.authService.events.subscribe(event => { 21 | if (event instanceof OAuthErrorEvent) { 22 | console.error(event); 23 | } else { 24 | console.warn(event); 25 | } 26 | }); 27 | ``` 28 | 29 | Here's a list of the main events you might encounter: 30 | 31 | - `discovery_document_loaded` is published whenever the service has retrieved the openid configuration and successfully saved the `jwks` information 32 | - `invalid_nonce_in_state` is published during `tryLogin`, when an access token has been requested and the state check was not disabled via the options, only in case the nonce is not as expected (see OAuth2 spec for more details about the nonce) 33 | - `user_profile_loaded` is published just before `loadUserProfile()` successfully resolves 34 | - `token_received` is published whenever the requested token(s) have been successfully received and stored 35 | - `silently_refreshed` is published when the silent refresh timer has gone off and the library has also successfully refreshed the tokens (only applicable to Implicit Flow) 36 | - `silent_refresh_timeout` is published if the silent refresh timer has gone off but it takes too long to successfully refresh 37 | - `session_error` will only be published if the session checks encounter an error 38 | 39 | For a full list of available events see the string based enum in [the file `events.ts`](https://github.com/manfredsteyer/angular-oauth2-oidc/blob/master/projects/lib/src/events.ts). 40 | -------------------------------------------------------------------------------- /docs-src/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Please find the [Getting Started Guide in the README](https://www.npmjs.com/package/angular-oauth2-oidc) above. -------------------------------------------------------------------------------- /docs-src/hash-strategy.md: -------------------------------------------------------------------------------- 1 | # Routing with the HashStrategy 2 | 3 | If you are leveraging the ``LocationStrategy`` which the Router is using by default, you can skip this section. 4 | 5 | When using the ``HashStrategy`` for Routing, the Router will override the received hash fragment with the tokens when it performs it initial navigation. This prevents the library from reading them. To avoid this, disable initial navigation when setting up the routes for your root module: 6 | 7 | ```TypeScript 8 | export let AppRouterModule = RouterModule.forRoot(APP_ROUTES, { 9 | useHash: true, 10 | initialNavigation: false 11 | }); 12 | ``` 13 | 14 | After tryLogin did its job, you can manually perform the initial navigation: 15 | 16 | ```TypeScript 17 | this.oauthService.tryLogin().then(_ => { 18 | this.router.navigate(['/']); 19 | }) 20 | ``` 21 | 22 | Another solution is the use a redirect uri that already contains the initial route. In this case the router will not override it. An example for such a redirect uri is 23 | 24 | ``` 25 | http://localhost:8080/#/home 26 | ``` 27 | -------------------------------------------------------------------------------- /docs-src/id-token-validation.md: -------------------------------------------------------------------------------- 1 | # Configure/ Adapt id_token Validation 2 | 3 | You can hook in an implementation of the interface ``TokenValidator`` to validate the signature of the received id_token and its at_hash property. This packages provides two implementations: 4 | 5 | - JwksValidationHandler 6 | - NullValidationHandler 7 | 8 | The former one validates the signature against public keys received via the discovery document (property jwks) and the later one skips the validation on client side. 9 | 10 | ```TypeScript 11 | import { JwksValidationHandler } from 'angular-oauth2-oidc'; 12 | 13 | [...] 14 | 15 | this.oauthService.tokenValidationHandler = new JwksValidationHandler(); 16 | ``` 17 | 18 | In cases where no ValidationHandler is defined, you receive a warning on the console. This means that the library wants you to explicitly decide on this. 19 | 20 | ## Dependency Injection 21 | 22 | You can also setup a ValidationHandler by leveraging dependency injection: 23 | 24 | ```TypeScript 25 | [...] 26 | providers: [ 27 | { provide: ValidationHandler, useClass: JwksValidationHandler }, 28 | ], 29 | [...] 30 | ``` -------------------------------------------------------------------------------- /docs-src/implicit-flow-config-discovery.md: -------------------------------------------------------------------------------- 1 | # Original Config API 2 | 3 | > This describes the older config API which is nowadays only supported for the sake of backwards compatibility. 4 | 5 | To configure the library you just have to set some properties on startup. For this, the following sample uses the constructor of the AppComponent which is called before routing kicks in. 6 | 7 | Please note that the following sample uses the original config API. For information about the new config API have a look to the project's README above. 8 | 9 | ```TypeScript 10 | @Component({ ... }) 11 | export class AppComponent { 12 | 13 | constructor(private oauthService: OAuthService) { 14 | 15 | // URL of the SPA to redirect the user to after login 16 | this.oauthService.redirectUri = window.location.origin + "/index.html"; 17 | 18 | // The SPA's id. The SPA is registerd with this id at the auth-server 19 | this.oauthService.clientId = "spa-demo"; 20 | 21 | // set the scope for the permissions the client should request 22 | // The first three are defined by OIDC. The 4th is a usecase-specific one 23 | this.oauthService.scope = "openid profile email voucher"; 24 | 25 | // The name of the auth-server that has to be mentioned within the token 26 | this.oauthService.issuer = "https://steyer-identity-server.azurewebsites.net/identity"; 27 | 28 | // Load Discovery Document and then try to login the user 29 | this.oauthService.loadDiscoveryDocument().then(() => { 30 | 31 | // This method just tries to parse the token(s) within the url when 32 | // the auth-server redirects the user back to the web-app 33 | // It dosn't send the user the the login page 34 | this.oauthService.tryLogin(); 35 | 36 | }); 37 | 38 | } 39 | 40 | } 41 | ``` 42 | 43 | If you find yourself receiving errors related to discovery document validation, your ID Provider may have OAuth2 endpoints that do not use the `issuer` value as a consistent base URL. You can turn off strict validation of discovery document endpoints for this scenario. See [Discovery Document Validation](https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/discovery-document-validation.html) for details. -------------------------------------------------------------------------------- /docs-src/implicit-flow-config-no-discovery.md: -------------------------------------------------------------------------------- 1 | # Configure Library for Implicit Flow (without discovery document) 2 | 3 | When you don't have a discovery document, you have to configure more properties manually: 4 | 5 | Please note that the following sample uses the original config API. For information about the new config API have a look to the project's README above. 6 | 7 | ```TypeScript 8 | @Component({ ... }) 9 | export class AppComponent { 10 | 11 | constructor(private oauthService: OAuthService) { 12 | 13 | // Login-Url 14 | this.oauthService.loginUrl = "https://steyer-identity-server.azurewebsites.net/identity/connect/authorize"; //Id-Provider? 15 | 16 | // URL of the SPA to redirect the user to after login 17 | this.oauthService.redirectUri = window.location.origin + "/index.html"; 18 | 19 | // The SPA's id. Register SPA with this id at the auth-server 20 | this.oauthService.clientId = "spa-demo"; 21 | 22 | // set the scope for the permissions the client should request 23 | this.oauthService.scope = "openid profile email voucher"; 24 | 25 | // Use setStorage to use sessionStorage or another implementation of the TS-type Storage 26 | // instead of localStorage 27 | this.oauthService.setStorage(sessionStorage); 28 | 29 | // To also enable single-sign-out set the url for your auth-server's logout-endpoint here 30 | this.oauthService.logoutUrl = "https://steyer-identity-server.azurewebsites.net/identity/connect/endsession"; 31 | 32 | // This method just tries to parse the token(s) within the url when 33 | // the auth-server redirects the user back to the web-app 34 | // It doesn't send the user the the login page 35 | this.oauthService.tryLogin(); 36 | 37 | 38 | } 39 | 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /docs-src/implicit-flow.md: -------------------------------------------------------------------------------- 1 | ## Configuring for Implicit Flow 2 | 3 | This section shows how to implement login leveraging implicit flow. This is the OAuth2/OIDC flow which was originally intended for Single Page Application. 4 | 5 | Meanwhile using **Code Flow** instead is a **best practice** and with OAuth 2.1 implicit flow will be **deprecated***. 6 | 7 | ```TypeScript 8 | import { AuthConfig } from 'angular-oauth2-oidc'; 9 | 10 | export const authConfig: AuthConfig = { 11 | // Url of the Identity Provider 12 | issuer: 'https://steyer-identity-server.azurewebsites.net/identity', 13 | 14 | // URL of the SPA to redirect the user to after login 15 | redirectUri: window.location.origin + '/index.html', 16 | 17 | // The SPA's id. The SPA is registered with this id at the auth-server 18 | clientId: 'spa-demo', 19 | 20 | // set the scope for the permissions the client should request 21 | // The first three are defined by OIDC. The 4th is a usecase-specific one 22 | scope: 'openid profile email voucher', 23 | } 24 | ``` 25 | 26 | Configure the ``OAuthService`` with this config object when the application starts up: 27 | 28 | ```TypeScript 29 | import { OAuthService } from 'angular-oauth2-oidc'; 30 | import { JwksValidationHandler } from 'angular-oauth2-oidc'; 31 | import { authConfig } from './auth.config'; 32 | import { Component } from '@angular/core'; 33 | 34 | @Component({ 35 | selector: 'flight-app', 36 | templateUrl: './app.component.html' 37 | }) 38 | export class AppComponent { 39 | 40 | constructor(private oauthService: OAuthService) { 41 | this.configure(); 42 | } 43 | 44 | private configure() { 45 | this.oauthService.configure(authConfig); 46 | this.oauthService.tokenValidationHandler = new JwksValidationHandler(); 47 | this.oauthService.loadDiscoveryDocumentAndTryLogin(); 48 | } 49 | } 50 | ``` 51 | 52 | ### Implementing a Login Form 53 | 54 | After you've configured the library, you just have to call ``initImplicitFlow`` to login using OAuth2/ OIDC. 55 | 56 | ```TypeScript 57 | import { Component } from '@angular/core'; 58 | import { OAuthService } from 'angular-oauth2-oidc'; 59 | 60 | @Component({ 61 | templateUrl: "app/home.html" 62 | }) 63 | export class HomeComponent { 64 | 65 | constructor(private oauthService: OAuthService) { 66 | } 67 | 68 | public login() { 69 | this.oauthService.initLoginFlow(); 70 | } 71 | 72 | public logoff() { 73 | this.oauthService.logOut(); 74 | } 75 | 76 | public get name() { 77 | let claims = this.oauthService.getIdentityClaims(); 78 | if (!claims) return null; 79 | return claims.given_name; 80 | } 81 | 82 | } 83 | ``` 84 | 85 | The following snippet contains the template for the login page: 86 | 87 | ```HTML 88 |

89 | Hallo 90 |

91 |

92 | Hallo, {{name}} 93 |

94 | 95 | 98 | 101 | 102 |
103 | Username/Passwort zum Testen: max/geheim 104 |
105 | ``` 106 | 107 | -------------------------------------------------------------------------------- /docs-src/interceptors.md: -------------------------------------------------------------------------------- 1 | # Interceptors 2 | 3 | Since 3.1 the library uses a default HttpInterceptor that takes care about transmitting the access_token to the resource server and about error handling for security related errors (HTTP status codes 401 and 403) received from the resource server. To put in on, just set ``sendAccessToken`` to ``true`` and set ``allowedUrls`` to an array with prefixes for the respective urls. Use lower case for the prefixes: 4 | 5 | ```TypeScript 6 | OAuthModule.forRoot({ 7 | resourceServer: { 8 | allowedUrls: ['http://www.angular.at/api'], 9 | sendAccessToken: true 10 | } 11 | }) 12 | ``` 13 | 14 | You can provide an error handler for the urls white listed here by provding a service for the token ``OAuthResourceServerErrorHandler``. 15 | 16 | To implement such a service, implement the abstract class ``OAuthResourceServerErrorHandler``. The following example shows the default implemantion that just passes the cought error through: 17 | 18 | ```TypeScript 19 | export class OAuthNoopResourceServerErrorHandler implements OAuthResourceServerErrorHandler { 20 | 21 | handleError(err: HttpResponse): Observable { 22 | return _throw(err); 23 | } 24 | 25 | } 26 | ``` 27 | 28 | ## Custom Interceptors 29 | 30 | Feel free to write custom interceptors but keep in mind that injecting the ``OAuthService`` into them creates a circular dependency which leads to an error. The easiest way to prevent this is to use the OAuthStorage directly which also provides the access_token: 31 | 32 | ```TypeScript 33 | import { Injectable, Inject, Optional } from '@angular/core'; 34 | import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc'; 35 | import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse, HttpErrorResponse } from '@angular/common/http'; 36 | import {Observable} from 'rxjs/Observable'; 37 | import { OAuthResourceServerErrorHandler } from "./resource-server-error-handler"; 38 | import { OAuthModuleConfig } from "../oauth-module.config"; 39 | 40 | import 'rxjs/add/operator/catch'; 41 | 42 | @Injectable() 43 | export class DefaultOAuthInterceptor implements HttpInterceptor { 44 | 45 | constructor( 46 | private authStorage: OAuthStorage, 47 | private errorHandler: OAuthResourceServerErrorHandler, 48 | @Optional() private moduleConfig: OAuthModuleConfig 49 | ) { 50 | } 51 | 52 | private checkUrl(url: string): boolean { 53 | let found = this.moduleConfig.resourceServer.allowedUrls.find(u => url.startsWith(u)); 54 | return !!found; 55 | } 56 | 57 | public intercept(req: HttpRequest, next: HttpHandler): Observable> { 58 | 59 | let url = req.url.toLowerCase(); 60 | 61 | if (!this.moduleConfig) return next.handle(req); 62 | if (!this.moduleConfig.resourceServer) return next.handle(req); 63 | if (!this.moduleConfig.resourceServer.allowedUrls) return next.handle(req); 64 | if (!this.checkUrl(url)) return next.handle(req); 65 | 66 | let sendAccessToken = this.moduleConfig.resourceServer.sendAccessToken; 67 | 68 | if (sendAccessToken) { 69 | 70 | let token = this.authStorage.getItem('access_token'); 71 | let header = 'Bearer ' + token; 72 | 73 | let headers = req.headers 74 | .set('Authorization', header); 75 | 76 | req = req.clone({ headers }); 77 | } 78 | 79 | return next.handle(req).catch(err => this.errorHandler.handleError(err)); 80 | 81 | } 82 | 83 | } 84 | ``` -------------------------------------------------------------------------------- /docs-src/popup.md: -------------------------------------------------------------------------------- 1 | # Logging in With a Popup 2 | 3 | Thanks to a great community contribution, this library also supports logging the user in via a popup. For this, you need to do two things: 4 | 5 | - Use ``initLoginFlowInPopup`` instead of ``initLoginFlow``. 6 | - Create and configure a ``silent-refresh.html`` as described [here](./silent-refresh.md) *. 7 | 8 | \* Please note this does not mean that you have to use silent refresh too. 9 | 10 | Also, for your ``silent-refresh.html``, make sure you are also targeting 11 | ``window.opener`` and fall back to ``window.parent``: 12 | 13 | **Please note**: IE sets opener to null under specific security settings. This prevents making this work. 14 | -------------------------------------------------------------------------------- /docs-src/preserving-state.md: -------------------------------------------------------------------------------- 1 | # Preserving State (like the Requested URL) 2 | 3 | When calling ``initImplicitFlow``, you can pass an optional state which could be the requested url: 4 | 5 | ```TypeScript 6 | this.oauthService.initImplicitFlow('http://www.myurl.com/x/y/z'); 7 | ``` 8 | 9 | After login succeeded, you can read this state: 10 | 11 | ```TypeScript 12 | this.oauthService.tryLogin({ 13 | onTokenReceived: (info) => { 14 | console.debug('state', info.state); 15 | } 16 | }) 17 | ``` 18 | -------------------------------------------------------------------------------- /docs-src/server-side-rendering.md: -------------------------------------------------------------------------------- 1 | # Server Side Rendering 2 | 3 | There is a great blog post that shows how this library can be used together with server side rendering: 4 | 5 | https://medium.com/lankapura/angular-server-side-rendering-for-authenticated-users-a021627fd9d3 6 | 7 | The sample for this can be found here: 8 | 9 | https://github.com/lankaapura/Angular-AspNetCore-Idsvr 10 | 11 | -------------------------------------------------------------------------------- /docs-src/session-checks.md: -------------------------------------------------------------------------------- 1 | # Session Checks 2 | 3 | Beginning with version 2.1, you can receive a notification when the user signs out with the identity provider. 4 | This is implemented as defined by the OpenID Connect Session Management 1.0 spec. 5 | 6 | When this option is activated, the library also automatically ends your local session. This means, the current tokens 7 | are deleted by calling ``logOut``. In addition to that, the library sends a session_terminated event, you can register 8 | for to perform a custom action. 9 | 10 | Please note that this option can only be used when also the identity provider in question supports it. 11 | 12 | ## Configuration 13 | 14 | To activate the session checks that leads to the mentioned notifications, set the configuration property 15 | ``sessionChecksEnabled``: 16 | 17 | ```TypeScript 18 | import { AuthConfig } from 'angular-oauth2-oidc'; 19 | 20 | export const authConfig: AuthConfig = { 21 | issuer: 'https://steyer-identity-server.azurewebsites.net/identity', 22 | redirectUri: window.location.origin + '/index.html', 23 | silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', 24 | clientId: 'spa-demo', 25 | scope: 'openid profile email voucher', 26 | 27 | // Activate Session Checks: 28 | sessionChecksEnabled: true, 29 | } 30 | ``` 31 | 32 | ## Refresh 33 | 34 | Please note that the lib performs a token refresh when the session changes to get the newest information about the current session. When using implicit flow, this means you have to configure [silent refresh](./silent-refresh.html); when using code flow you either need silent refresh or a [refresh token](./refreshing-a-token.html). 35 | 36 | If using refresh tokens, your Auth Server needs to bind them to the current session's lifetime. Unfortunately, the used version of Identity Server 4, shown in the docs and in the example applications, does not support this at the moment. 37 | 38 | ## Events 39 | To get notified, you can hook up for the event ``session_terminated``: 40 | 41 | ```TypeScript 42 | this.oauthService.events.pipe(filter(e => e.type === 'session_terminated')).subscribe(e => { 43 | console.debug('Your session has been terminated!'); 44 | }) 45 | ``` 46 | -------------------------------------------------------------------------------- /docs-src/skipping-login-form.md: -------------------------------------------------------------------------------- 1 | # Manually Skipping the Login Form 2 | 3 | First, try to use the ``loadDiscoveryDocumentAndLogin`` method instead of ``loadDiscoveryDocumentAndTryLogin``. If you need more control, the following could be interesting for you. 4 | 5 | ```TypeScript 6 | this.oauthService 7 | .loadDiscoveryDocumentAndTryLogin(/* { your LoginOptions }*/) // checks to see if the current url contains id token and access token 8 | .(hasReceivedTokens => { 9 | // this would have stored all the tokens needed 10 | if (hasReceivedTokens) { 11 | // carry on with your app 12 | return Promise.resolve(); 13 | 14 | /* if you wish to do something when the user receives tokens from the identity server, 15 | * use the event stream or the `onTokenReceived` callback in LoginOptions. 16 | * 17 | * this.oauthService.events(filter(e => e.type === 'token_received')).subscribe() 18 | */ 19 | } else { 20 | // may want to check if you were previously authenticated 21 | if (this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken()) { 22 | return Promise.resolve(); 23 | } else { 24 | // to safe guard this from progressing through the calling promise, 25 | // resolve it when it directed to the sign up page 26 | return new Promise(resolve => { 27 | this.oauthService.initLoginFlow(); 28 | // example if you are using explicit flow 29 | this.window.addEventListener('unload', () => { 30 | resolve(true); 31 | }); 32 | }); 33 | } 34 | } 35 | }) 36 | ``` -------------------------------------------------------------------------------- /docs-src/summary.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Getting Started", 4 | "file": "getting-started.md" 5 | }, 6 | { 7 | "title": "Preserving State (like the Requested URL)", 8 | "file": "preserving-state.md" 9 | }, 10 | { 11 | "title": "Refreshing a Token", 12 | "file": "token-refresh.md" 13 | }, 14 | { 15 | "title": "Silent Refresh", 16 | "file": "silent-refresh.md" 17 | }, 18 | { 19 | "title": "Token Refresh", 20 | "file": "token-refresh.md" 21 | }, 22 | { 23 | "title": "Working with HttpInterceptors", 24 | "file": "interceptors.md" 25 | }, 26 | { 27 | "title": "Callback after login", 28 | "file": "callback-after-login.md" 29 | }, 30 | { 31 | "title": "Popup-based Login", 32 | "file": "popup.md" 33 | }, 34 | { 35 | "title": "Custom Query Parameters", 36 | "file": "custom-query-parameter.md" 37 | }, 38 | { 39 | "title": "Events", 40 | "file": "events.md" 41 | }, 42 | { 43 | "title": "Routing with the HashStrategy", 44 | "file": "hash-strategy.md" 45 | }, 46 | { 47 | "title": "Adapt id_token Validation", 48 | "file": "id-token-validation.md" 49 | }, 50 | { 51 | "title": "Session Checks", 52 | "file": "session-checks.md" 53 | }, 54 | { 55 | "title": "Server Side Rendering", 56 | "file": "server-side-rendering.md" 57 | }, 58 | { 59 | "title": "Configure Library for Implicit Flow without discovery document", 60 | "file": "implicit-flow-config-no-discovery.md" 61 | }, 62 | { 63 | "title": "Using an ID Provider that Fails Discovery Document Validation", 64 | "file": "discovery-document-validation.md" 65 | }, 66 | { 67 | "title": "Using SystemJS", 68 | "file": "systemjs.md" 69 | }, 70 | { 71 | "title": "Using Implicit Flow", 72 | "file": "implicit-flow.md" 73 | }, 74 | { 75 | "title": "Using Password Flow", 76 | "file": "password-flow.md" 77 | }, 78 | { 79 | "title": "Configure custom OAuthStorage", 80 | "file": "configure-custom-oauthstorage.md" 81 | }, 82 | { 83 | "title": "Manually Skipping Login Form", 84 | "file": "skipping-login-form.md" 85 | }, 86 | { 87 | "title": "Original Config API", 88 | "file": "implicit-flow-config-discovery.md" 89 | }, 90 | { 91 | "title": "Authorization Servers", 92 | "file": "authsvr.md", 93 | "children": [ 94 | { 95 | "title": "Using Identity Server", 96 | "file": "authsvr-idsvr.md" 97 | }, 98 | { 99 | "title": "Using Keycloak", 100 | "file": "authsvr-keycloak.md" 101 | }, 102 | { 103 | "title": "Auth0", 104 | "file": "authsvr-auth0.md" 105 | }, 106 | { 107 | "title": "Azure AD (Active Directory)", 108 | "file": "authsvr-azure-ad.md" 109 | } 110 | ] 111 | } 112 | 113 | ] 114 | -------------------------------------------------------------------------------- /docs-src/systemjs.md: -------------------------------------------------------------------------------- 1 | # Using SystemJS 2 | 3 | Thanks to [Kevin BEAUGRAND](https://github.com/kbeaugrand) for adding this information regarding SystemJS. 4 | 5 | ```TypeScript 6 | System.config({ 7 | ... 8 | meta: { 9 | 'angular-oauth2-oidc': { 10 | deps: ['jsrsasign'] 11 | }, 12 | } 13 | ... 14 | }); 15 | ``` 16 | 17 | Also thanks to [ppanthony](https://github.com/ppanthony) for sharing his SystemJS config: 18 | 19 | ``` 20 | 'angular-oauth2-oidc': { 21 | main: 'angular-oauth2-oidc.umd.js', 22 | format: 'cjs', 23 | defaultExtension: 'js', 24 | map: { 25 | 'jsrsasign': '/node_modules/jsrsasign/lib/jsrsasign', 26 | }, 27 | meta: { 28 | 'angular-oauth2-oidc': { 29 | deps: ['require','jsrsasign'] 30 | }, 31 | } 32 | } 33 | ``` -------------------------------------------------------------------------------- /docs-src/token-refresh.md: -------------------------------------------------------------------------------- 1 | # Refreshing a Token using Code Flow (not Implicit Flow!) 2 | 3 | When using code flow, you can get an ``refresh_token``. While the original standard DOES NOT allow this for SPAs, the mentioned [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13) document proposes to ease this limitation. However, it specifies a list of requirements one should take care about before using ``refresh_tokens``. Please make sure you respect those requirements. 4 | 5 | Please also note, that you have to request the ``offline_access`` scope to get a refresh token. 6 | 7 | To refresh your token, just call the ``refreshToken`` method: 8 | 9 | ```typescript 10 | this.oauthService.refreshToken(); 11 | ``` 12 | 13 | 14 | ## Automatically refreshing a token when/ before it expires (Code Flow and Implicit Flow) 15 | 16 | To automatically refresh a token when/ some time before it expires, just call the following method after configuring the ``OAuthService``: 17 | 18 | ```TypeScript 19 | this.oauthService.setupAutomaticSilentRefresh(); 20 | ``` 21 | 22 | By default, this event is fired after 75% of the token's life time is over. You can adjust this factor by setting the property ``timeoutFactor`` to a value between 0 and 1. For instance, 0.5 means, that the event is fired after half of the life time is over and 0.33 triggers the event after a third. 23 | -------------------------------------------------------------------------------- /docs/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/ionicons.eot -------------------------------------------------------------------------------- /docs/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/ionicons.ttf -------------------------------------------------------------------------------- /docs/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/ionicons.woff -------------------------------------------------------------------------------- /docs/fonts/ionicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/ionicons.woff2 -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-300.eot -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-300.ttf -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-300.woff -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-300.woff2 -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-700.eot -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-700.ttf -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-700.woff -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-700.woff2 -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-italic.eot -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-italic.ttf -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-italic.woff -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-italic.woff2 -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-regular.eot -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-regular.woff -------------------------------------------------------------------------------- /docs/fonts/roboto-v15-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/fonts/roboto-v15-latin-regular.woff2 -------------------------------------------------------------------------------- /docs/graph/dependencies.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | dependencies 11 | 12 | Legend 13 | 14 |  Declarations 15 | 16 |  Module 17 | 18 |  Bootstrap 19 | 20 |  Providers 21 | 22 |  Exports 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/images/compodoc-vectorise-inverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/images/compodoc-vectorise-inverted.png -------------------------------------------------------------------------------- /docs/images/compodoc-vectorise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/images/compodoc-vectorise.png -------------------------------------------------------------------------------- /docs/images/coverage-badge-documentation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | documentation 7 | 20% 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/docs/images/favicon.ico -------------------------------------------------------------------------------- /docs/js/compodoc.js: -------------------------------------------------------------------------------- 1 | var compodoc = { 2 | EVENTS: { 3 | READY: 'compodoc.ready', 4 | SEARCH_READY: 'compodoc.search.ready' 5 | } 6 | }; 7 | 8 | Object.assign( compodoc, EventDispatcher.prototype ); 9 | 10 | document.addEventListener('DOMContentLoaded', function() { 11 | compodoc.dispatchEvent({ 12 | type: compodoc.EVENTS.READY 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /docs/js/lazy-load-graphs.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | var lazyGraphs = [].slice.call(document.querySelectorAll('[lazy]')); 3 | var active = false; 4 | 5 | var lazyLoad = function() { 6 | if (active === false) { 7 | active = true; 8 | 9 | setTimeout(function() { 10 | lazyGraphs.forEach(function(lazyGraph) { 11 | if ( 12 | lazyGraph.getBoundingClientRect().top <= window.innerHeight && 13 | lazyGraph.getBoundingClientRect().bottom >= 0 && 14 | getComputedStyle(lazyGraph).display !== 'none' 15 | ) { 16 | lazyGraph.data = lazyGraph.getAttribute('lazy'); 17 | lazyGraph.removeAttribute('lazy'); 18 | 19 | lazyGraphs = lazyGraphs.filter(function(image) { return image !== lazyGraph}); 20 | 21 | if (lazyGraphs.length === 0) { 22 | document.removeEventListener('scroll', lazyLoad); 23 | window.removeEventListener('resize', lazyLoad); 24 | window.removeEventListener('orientationchange', lazyLoad); 25 | } 26 | } 27 | }); 28 | 29 | active = false; 30 | }, 200); 31 | } 32 | }; 33 | 34 | // initial load 35 | lazyLoad(); 36 | 37 | var container = document.querySelector('.container-fluid.modules'); 38 | if (container) { 39 | container.addEventListener('scroll', lazyLoad); 40 | window.addEventListener('resize', lazyLoad); 41 | window.addEventListener('orientationchange', lazyLoad); 42 | } 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /docs/js/libs/EventDispatcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | var EventDispatcher=function(){};Object.assign(EventDispatcher.prototype,{addEventListener:function(i,t){void 0===this._listeners&&(this._listeners={});var e=this._listeners;void 0===e[i]&&(e[i]=[]),-1===e[i].indexOf(t)&&e[i].push(t)},hasEventListener:function(i,t){if(void 0===this._listeners)return!1;var e=this._listeners;return void 0!==e[i]&&-1!==e[i].indexOf(t)},removeEventListener:function(i,t){if(void 0!==this._listeners){var e=this._listeners[i];if(void 0!==e){var s=e.indexOf(t);-1!==s&&e.splice(s,1)}}},dispatchEvent:function(i){if(void 0!==this._listeners){var t=this._listeners[i.type];if(void 0!==t){i.target=this;var e=[],s=0,n=t.length;for(s=0;s",">"));else if(1==i){if(r.push("<",e.tagName),e.hasAttributes())for(var n=e.attributes,s=0,o=n.length;s");for(var h=e.childNodes,s=0,o=h.length;s")}else r.push("/>")}else{if(8!=i)throw"Error serializing XML. Unhandled node of type: "+i;r.push("\x3c!--",e.nodeValue,"--\x3e")}};Object.defineProperty(e.prototype,"innerHTML",{get:function(){for(var e=[],r=this.firstChild;r;)t(r,e),r=r.nextSibling;return e.join("")},set:function(e){for(;this.firstChild;)this.removeChild(this.firstChild);try{var t=new DOMParser;t.async=!1,sXML=""+e+"";for(var r=t.parseFromString(sXML,"text/xml").documentElement.firstChild;r;)this.appendChild(this.ownerDocument.importNode(r,!0)),r=r.nextSibling}catch(e){throw new Error("Error parsing XML string")}}})}}((0,eval)("this").SVGElement); -------------------------------------------------------------------------------- /docs/js/libs/promise.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2013 (c) Pierre Duquesne 3 | * Licensed under the New BSD License. 4 | * https://github.com/stackp/promisejs 5 | */ 6 | (function(a){function b(){this._callbacks=[];}b.prototype.then=function(a,c){var d;if(this._isdone)d=a.apply(c,this.result);else{d=new b();this._callbacks.push(function(){var b=a.apply(c,arguments);if(b&&typeof b.then==='function')b.then(d.done,d);});}return d;};b.prototype.done=function(){this.result=arguments;this._isdone=true;for(var a=0;a=300)&&j.status!==304);h.done(a,j.responseText,j);}};j.send(k);return h;}function h(a){return function(b,c,d){return g(a,b,c,d);};}var i={Promise:b,join:c,chain:d,ajax:g,get:h('GET'),post:h('POST'),put:h('PUT'),del:h('DELETE'),ENOXHR:1,ETIMEOUT:2,ajaxTimeout:0};if(typeof define==='function'&&define.amd)define(function(){return i;});else a.promise=i;})(this); -------------------------------------------------------------------------------- /docs/js/libs/tablesort.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * tablesort v5.2.1 (2021-10-30) 3 | * http://tristen.ca/tablesort/demo/ 4 | * Copyright (c) 2021 ; Licensed MIT 5 | */ 6 | !function(){function a(b,c){if(!(this instanceof a))return new a(b,c);if(!b||"TABLE"!==b.tagName)throw new Error("Element must be a table");this.init(b,c||{})}var b=[],c=function(a){var b;return window.CustomEvent&&"function"==typeof window.CustomEvent?b=new CustomEvent(a):(b=document.createEvent("CustomEvent"),b.initCustomEvent(a,!1,!1,void 0)),b},d=function(a,b){return a.getAttribute(b.sortAttribute||"data-sort")||a.textContent||a.innerText||""},e=function(a,b){return a=a.trim().toLowerCase(),b=b.trim().toLowerCase(),a===b?0:a0)if(a.tHead&&a.tHead.rows.length>0){for(e=0;e0&&n.push(m),o++;if(!n)return}for(o=0;o 0) { 9 | tabs = tabs[0].querySelectorAll('li'); 10 | for (var i = 0; i < tabs.length; i++) { 11 | tabs[i].addEventListener('click', updateAddress); 12 | var linkTag = tabs[i].querySelector('a'); 13 | if (location.hash !== '') { 14 | var currentHash = location.hash.substr(1); 15 | if (currentHash === linkTag.dataset.link) { 16 | linkTag.click(); 17 | } 18 | } 19 | } 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /docs/styles/dark.css: -------------------------------------------------------------------------------- 1 | body.dark { 2 | background: #212121; 3 | color: #fafafa; 4 | } 5 | 6 | .dark code { 7 | color: #e09393; 8 | } 9 | 10 | .dark a, 11 | .dark .menu ul.list li a.active { 12 | color: #7fc9ff; 13 | } 14 | 15 | .dark .menu { 16 | background: #212121; 17 | border-right: 1px solid #444; 18 | } 19 | 20 | .dark .menu ul.list li a { 21 | color: #fafafa; 22 | } 23 | 24 | .dark .menu ul.list li.divider { 25 | background: #444; 26 | } 27 | 28 | .dark .xs-menu ul.list li:nth-child(2) { 29 | margin: 0; 30 | background: none; 31 | } 32 | 33 | .dark .menu ul.list li:nth-child(2) { 34 | margin: 0; 35 | background: none; 36 | } 37 | 38 | .dark #book-search-input { 39 | background: #212121; 40 | border-top: 1px solid #444; 41 | border-bottom: 1px solid #444; 42 | color: #fafafa; 43 | } 44 | 45 | .dark .table.metadata > tbody > tr:hover { 46 | color: #555; 47 | } 48 | 49 | .dark .table-bordered { 50 | border: 1px solid #444; 51 | } 52 | 53 | .dark .table-bordered > tbody > tr > td, 54 | .dark .table-bordered > tbody > tr > th, 55 | .dark .table-bordered > tfoot > tr > td, 56 | .dark .table-bordered > tfoot > tr > th, 57 | .dark .table-bordered > thead > tr > td, 58 | .dark .table-bordered > thead > tr > th { 59 | border: 1px solid #444; 60 | } 61 | 62 | .dark .coverage a, 63 | .dark .coverage-count { 64 | color: #fafafa; 65 | } 66 | 67 | .dark .coverage-header { 68 | color: black; 69 | } 70 | 71 | .dark .routes svg text, 72 | .dark .routes svg a { 73 | fill: white; 74 | } 75 | .dark .routes svg rect { 76 | fill: #212121 !important; 77 | } 78 | 79 | .dark .navbar-default, 80 | .dark .btn-default { 81 | background-color: black; 82 | border-color: #444; 83 | color: #fafafa; 84 | } 85 | 86 | .dark .navbar-default .navbar-brand { 87 | color: #fafafa; 88 | } 89 | 90 | .dark .overview .card, 91 | .dark .modules .card { 92 | background: #171717; 93 | color: #fafafa; 94 | border: 1px solid #444; 95 | } 96 | .dark .overview .card a { 97 | color: #fafafa; 98 | } 99 | 100 | .dark .modules .card-header { 101 | background: none; 102 | border-bottom: 1px solid #444; 103 | } 104 | 105 | .dark .module .list-group-item { 106 | background: none; 107 | border: 1px solid #444; 108 | } 109 | 110 | .dark .container-fluid.module h3 a { 111 | color: #337ab7; 112 | } 113 | 114 | .dark table.params thead { 115 | background: #484848; 116 | color: #fafafa; 117 | } 118 | 119 | .dark .content table { 120 | --bs-table-color: #fafafa; 121 | } 122 | -------------------------------------------------------------------------------- /docs/styles/laravel.css: -------------------------------------------------------------------------------- 1 | .nav-tabs > li > a { 2 | text-decoration: none; 3 | } 4 | 5 | .navbar-default .navbar-brand { 6 | color: #f4645f; 7 | text-decoration: none; 8 | font-size: 16px; 9 | } 10 | 11 | .menu ul.list li a[data-type='chapter-link'], 12 | .menu ul.list li.chapter .simple { 13 | color: #525252; 14 | border-bottom: 1px dashed rgba(0, 0, 0, 0.1); 15 | } 16 | 17 | .content h1, 18 | .content h2, 19 | .content h3, 20 | .content h4, 21 | .content h5 { 22 | color: #292e31; 23 | font-weight: normal; 24 | } 25 | 26 | .content { 27 | color: #4c555a; 28 | } 29 | 30 | a { 31 | color: #f4645f; 32 | text-decoration: underline; 33 | } 34 | a:hover { 35 | color: #f1362f; 36 | } 37 | 38 | .menu ul.list li:nth-child(2) { 39 | margin-top: 0; 40 | } 41 | 42 | .menu ul.list li.title a { 43 | color: #f4645f; 44 | text-decoration: none; 45 | font-size: 16px; 46 | } 47 | 48 | .menu ul.list li a { 49 | color: #f4645f; 50 | text-decoration: none; 51 | } 52 | .menu ul.list li a.active { 53 | color: #f4645f; 54 | font-weight: bold; 55 | } 56 | 57 | code { 58 | box-sizing: border-box; 59 | display: inline-block; 60 | padding: 0 5px; 61 | background: #f0f2f1; 62 | border-radius: 3px; 63 | color: #b93d6a; 64 | font-size: 13px; 65 | line-height: 20px; 66 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125); 67 | } 68 | 69 | pre { 70 | margin: 0; 71 | padding: 12px 12px; 72 | background: rgba(238, 238, 238, 0.35); 73 | border-radius: 3px; 74 | font-size: 13px; 75 | line-height: 1.5em; 76 | font-weight: 500; 77 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125); 78 | } 79 | 80 | .dark body { 81 | color: #fafafa; 82 | } 83 | .dark .content h1, 84 | .dark .content h2, 85 | .dark .content h3, 86 | .dark .content h4, 87 | .dark .content h5 { 88 | color: #fafafa; 89 | } 90 | 91 | .dark code { 92 | background: none; 93 | } 94 | 95 | .dark .content { 96 | color: #fafafa; 97 | } 98 | 99 | .dark .menu ul.list li a[data-type='chapter-link'], 100 | .dark .menu ul.list li.chapter .simple { 101 | color: #fafafa; 102 | } 103 | 104 | .dark .menu ul.list li.title a { 105 | color: #fafafa; 106 | } 107 | 108 | .dark .menu ul.list li a { 109 | color: #fafafa; 110 | } 111 | .dark .menu ul.list li a.active { 112 | color: #7fc9ff; 113 | } 114 | -------------------------------------------------------------------------------- /docs/styles/material.css: -------------------------------------------------------------------------------- 1 | .menu { 2 | background: none; 3 | } 4 | 5 | a:hover { 6 | text-decoration: none; 7 | } 8 | 9 | /** LINK **/ 10 | 11 | .menu ul.list li a { 12 | text-decoration: none; 13 | } 14 | 15 | .menu ul.list li a:hover, 16 | .menu ul.list li.chapter .simple:hover { 17 | background-color: #f8f9fa; 18 | text-decoration: none; 19 | } 20 | 21 | #book-search-input { 22 | margin-bottom: 0; 23 | } 24 | 25 | .menu ul.list li.divider { 26 | margin-top: 0; 27 | background: #e9ecef; 28 | } 29 | 30 | .menu .title:hover { 31 | background-color: #f8f9fa; 32 | } 33 | 34 | /** CARD **/ 35 | 36 | .card { 37 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 38 | 0 1px 5px 0 rgba(0, 0, 0, 0.12); 39 | border-radius: 0.125rem; 40 | border: 0; 41 | margin-top: 1px; 42 | } 43 | 44 | .card-header { 45 | background: none; 46 | } 47 | 48 | /** BUTTON **/ 49 | 50 | .btn { 51 | border-radius: 0.125rem; 52 | } 53 | 54 | /** NAV BAR **/ 55 | 56 | .nav { 57 | border: 0; 58 | } 59 | .nav-tabs > li > a { 60 | border: 0; 61 | border-bottom: 0.214rem solid transparent; 62 | color: rgba(0, 0, 0, 0.54); 63 | margin-right: 0; 64 | } 65 | .nav-tabs > li.active > a, 66 | .nav-tabs > li.active > a:focus, 67 | .nav-tabs > li.active > a:hover { 68 | color: rgba(0, 0, 0, 0.87); 69 | border-top: 0; 70 | border-left: 0; 71 | border-right: 0; 72 | border-bottom: 0.214rem solid transparent; 73 | border-color: #008cff; 74 | font-weight: bold; 75 | } 76 | .nav > li > a:focus, 77 | .nav > li > a:hover { 78 | background: none; 79 | } 80 | 81 | /** LIST **/ 82 | 83 | .list-group-item:first-child { 84 | border-top-left-radius: 0.125rem; 85 | border-top-right-radius: 0.125rem; 86 | } 87 | .list-group-item:last-child { 88 | border-bottom-left-radius: 0.125rem; 89 | border-bottom-right-radius: 0.125rem; 90 | } 91 | 92 | /** MISC **/ 93 | 94 | .modifier { 95 | border-radius: 0.125rem; 96 | } 97 | 98 | pre[class*='language-'] { 99 | border-radius: 0.125rem; 100 | } 101 | 102 | /** TABLE **/ 103 | 104 | .table-hover > tbody > tr:hover { 105 | background: rgba(0, 0, 0, 0.075); 106 | } 107 | 108 | table.params thead { 109 | background: none; 110 | } 111 | table.params thead td { 112 | color: rgba(0, 0, 0, 0.54); 113 | font-weight: bold; 114 | } 115 | 116 | .dark .menu .title:hover { 117 | background-color: #2d2d2d; 118 | } 119 | .dark .menu ul.list li a:hover, 120 | .dark .menu ul.list li.chapter .simple:hover { 121 | background-color: #2d2d2d; 122 | } 123 | .dark .nav-tabs > li:not(.active) > a { 124 | color: #fafafa; 125 | } 126 | .dark table.params thead { 127 | background: #484848; 128 | } 129 | .dark table.params thead td { 130 | color: #fafafa; 131 | } 132 | -------------------------------------------------------------------------------- /docs/styles/original.css: -------------------------------------------------------------------------------- 1 | .navbar-default .navbar-brand, 2 | .menu ul.list li.title { 3 | font-weight: bold; 4 | color: #3c3c3c; 5 | padding-bottom: 5px; 6 | } 7 | 8 | .menu ul.list li a[data-type='chapter-link'], 9 | .menu ul.list li.chapter .simple { 10 | font-weight: bold; 11 | font-size: 14px; 12 | } 13 | 14 | .menu ul.list li a[href='./routes.html'] { 15 | border-bottom: none; 16 | } 17 | 18 | .menu ul.list > li:nth-child(2) { 19 | display: none; 20 | } 21 | 22 | .menu ul.list li.chapter ul.links { 23 | background: #fff; 24 | padding-left: 0; 25 | } 26 | 27 | .menu ul.list li.chapter ul.links li { 28 | border-bottom: 1px solid #ddd; 29 | padding-left: 20px; 30 | } 31 | 32 | .menu ul.list li.chapter ul.links li:last-child { 33 | border-bottom: none; 34 | } 35 | 36 | .menu ul.list li a.active { 37 | color: #337ab7; 38 | font-weight: bold; 39 | } 40 | 41 | #book-search-input { 42 | margin-bottom: 0; 43 | border-bottom: none; 44 | } 45 | .menu ul.list li.divider { 46 | margin: 0; 47 | } 48 | 49 | .dark .menu ul.list li.chapter ul.links { 50 | background: none; 51 | } 52 | -------------------------------------------------------------------------------- /docs/styles/readthedocs.css: -------------------------------------------------------------------------------- 1 | .navbar-default { 2 | background: #2980b9; 3 | border: none; 4 | } 5 | 6 | .navbar-default .navbar-brand { 7 | color: #fcfcfc; 8 | } 9 | 10 | .menu { 11 | background: #343131; 12 | color: #fcfcfc; 13 | } 14 | 15 | .menu ul.list li a { 16 | color: #fcfcfc; 17 | } 18 | 19 | .menu ul.list li.title { 20 | background: #2980b9; 21 | padding-bottom: 5px; 22 | } 23 | 24 | .menu ul.list li:nth-child(2) { 25 | margin-top: 0; 26 | } 27 | 28 | .menu ul.list li.chapter a, 29 | .menu ul.list li.chapter .simple { 30 | color: #555; 31 | text-transform: uppercase; 32 | text-decoration: none; 33 | } 34 | 35 | .menu ul.list li.chapter ul.links a { 36 | color: #b3b3b3; 37 | text-transform: none; 38 | padding-left: 35px; 39 | } 40 | 41 | .menu ul.list li.chapter ul.links a:hover { 42 | background: #4e4a4a; 43 | } 44 | 45 | .menu ul.list li.chapter a.active, 46 | .menu ul.list li.chapter ul.links a.active { 47 | color: #0099e5; 48 | } 49 | 50 | .menu ul.list li.chapter ul.links { 51 | padding-left: 0; 52 | } 53 | 54 | .menu ul.list li.divider { 55 | background: rgba(255, 255, 255, 0.07); 56 | } 57 | 58 | #book-search-input input, 59 | #book-search-input input:focus, 60 | #book-search-input input:hover { 61 | color: #949494; 62 | } 63 | 64 | .copyright { 65 | color: #b3b3b3; 66 | background: #272525; 67 | } 68 | 69 | .content { 70 | background: #fcfcfc; 71 | } 72 | 73 | .content a { 74 | color: #2980b9; 75 | } 76 | 77 | .content a:hover { 78 | color: #3091d1; 79 | } 80 | 81 | .content a:visited { 82 | color: #9b59b6; 83 | } 84 | 85 | .menu ul.list li:nth-last-child(2) { 86 | background: none; 87 | } 88 | 89 | code { 90 | white-space: nowrap; 91 | max-width: 100%; 92 | background: #fff; 93 | padding: 2px 5px; 94 | color: #e74c3c; 95 | overflow-x: auto; 96 | border-radius: 0; 97 | } 98 | 99 | pre { 100 | white-space: pre; 101 | margin: 0; 102 | padding: 12px 12px; 103 | font-size: 12px; 104 | line-height: 1.5; 105 | display: block; 106 | overflow: auto; 107 | color: #404040; 108 | background: rgba(238, 238, 238, 0.35); 109 | } 110 | 111 | .dark .content { 112 | background: none; 113 | } 114 | .dark code { 115 | background: none; 116 | color: #e09393; 117 | } 118 | -------------------------------------------------------------------------------- /docs/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | table, 60 | caption, 61 | tbody, 62 | tfoot, 63 | thead, 64 | tr, 65 | th, 66 | td, 67 | article, 68 | aside, 69 | canvas, 70 | details, 71 | embed, 72 | figure, 73 | figcaption, 74 | footer, 75 | header, 76 | hgroup, 77 | menu, 78 | nav, 79 | output, 80 | ruby, 81 | section, 82 | summary, 83 | time, 84 | mark, 85 | audio, 86 | video { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font: inherit; 91 | font-size: 100%; 92 | vertical-align: baseline; 93 | } 94 | /* HTML5 display-role reset for older browsers */ 95 | article, 96 | aside, 97 | details, 98 | figcaption, 99 | figure, 100 | footer, 101 | header, 102 | hgroup, 103 | menu, 104 | nav, 105 | section { 106 | display: block; 107 | } 108 | body { 109 | line-height: 1; 110 | } 111 | ol, 112 | ul { 113 | list-style: none; 114 | } 115 | blockquote, 116 | q { 117 | quotes: none; 118 | } 119 | blockquote:before, 120 | blockquote:after, 121 | q:before, 122 | q:after { 123 | content: ''; 124 | content: none; 125 | } 126 | table { 127 | border-collapse: collapse; 128 | border-spacing: 0; 129 | } 130 | -------------------------------------------------------------------------------- /docs/styles/stripe.css: -------------------------------------------------------------------------------- 1 | .navbar-default .navbar-brand { 2 | color: #0099e5; 3 | } 4 | 5 | .menu ul.list li a[data-type='chapter-link'], 6 | .menu ul.list li.chapter .simple { 7 | color: #939da3; 8 | text-transform: uppercase; 9 | } 10 | 11 | .content h1, 12 | .content h2, 13 | .content h3, 14 | .content h4, 15 | .content h5 { 16 | color: #292e31; 17 | font-weight: normal; 18 | } 19 | 20 | .content { 21 | color: #4c555a; 22 | } 23 | 24 | .menu ul.list li.title { 25 | padding: 5px 0; 26 | } 27 | 28 | a { 29 | color: #0099e5; 30 | text-decoration: none; 31 | } 32 | a:hover { 33 | color: #292e31; 34 | text-decoration: none; 35 | } 36 | 37 | .menu ul.list li:nth-child(2) { 38 | margin-top: 0; 39 | } 40 | 41 | .menu ul.list li.title a, 42 | .navbar a { 43 | color: #0099e5; 44 | text-decoration: none; 45 | font-size: 16px; 46 | } 47 | 48 | .menu ul.list li a.active { 49 | color: #0099e5; 50 | } 51 | 52 | code { 53 | box-sizing: border-box; 54 | display: inline-block; 55 | padding: 0 5px; 56 | background: #fafcfc; 57 | border-radius: 4px; 58 | color: #b93d6a; 59 | font-size: 13px; 60 | line-height: 20px; 61 | } 62 | 63 | pre { 64 | margin: 0; 65 | padding: 12px 12px; 66 | background: #272b2d; 67 | border-radius: 5px; 68 | font-size: 13px; 69 | line-height: 1.5em; 70 | font-weight: 500; 71 | } 72 | 73 | .dark body { 74 | color: #fafafa; 75 | } 76 | .dark .content h1, 77 | .dark .content h2, 78 | .dark .content h3, 79 | .dark .content h4, 80 | .dark .content h5 { 81 | color: #fafafa; 82 | } 83 | 84 | .dark code { 85 | background: none; 86 | } 87 | 88 | .dark .content { 89 | color: #fafafa; 90 | } 91 | 92 | .dark .menu ul.list li a[data-type='chapter-link'], 93 | .dark .menu ul.list li.chapter .simple { 94 | color: #fafafa; 95 | } 96 | 97 | .dark .menu ul.list li.title a { 98 | color: #fafafa; 99 | } 100 | 101 | .dark .menu ul.list li a { 102 | color: #fafafa; 103 | } 104 | .dark .menu ul.list li a.active { 105 | color: #7fc9ff; 106 | } 107 | -------------------------------------------------------------------------------- /docs/styles/style.css: -------------------------------------------------------------------------------- 1 | @import "./reset.css"; 2 | @import "./bootstrap.min.css"; 3 | @import "./bootstrap-card.css"; 4 | @import "./prism.css"; 5 | @import "./ionicons.min.css"; 6 | @import "./compodoc.css"; 7 | @import "./tablesort.css"; 8 | -------------------------------------------------------------------------------- /docs/styles/tablesort.css: -------------------------------------------------------------------------------- 1 | th[role=columnheader]:not(.no-sort) { 2 | cursor: pointer; 3 | } 4 | 5 | th[role=columnheader]:not(.no-sort):after { 6 | content: ''; 7 | float: right; 8 | margin-top: 7px; 9 | border-width: 0 4px 4px; 10 | border-style: solid; 11 | border-color: #404040 transparent; 12 | visibility: visible; 13 | opacity: 1; 14 | -ms-user-select: none; 15 | -webkit-user-select: none; 16 | -moz-user-select: none; 17 | user-select: none; 18 | } 19 | 20 | th[aria-sort=ascending]:not(.no-sort):after { 21 | border-bottom: none; 22 | border-width: 4px 4px 0; 23 | } 24 | 25 | th[aria-sort]:not(.no-sort):after { 26 | visibility: visible; 27 | opacity: 0.4; 28 | } 29 | 30 | th[role=columnheader]:not(.no-sort):hover:after { 31 | visibility: visible; 32 | opacity: 1; 33 | } 34 | -------------------------------------------------------------------------------- /docs/styles/vagrant.css: -------------------------------------------------------------------------------- 1 | .navbar-default .navbar-brand { 2 | background: white; 3 | color: #8d9ba8; 4 | } 5 | 6 | .menu .list { 7 | background: #0c5593; 8 | } 9 | 10 | .menu .chapter { 11 | padding: 0 20px; 12 | } 13 | 14 | .menu ul.list li a[data-type='chapter-link'], 15 | .menu ul.list li.chapter .simple { 16 | color: white; 17 | text-transform: uppercase; 18 | border-bottom: 1px solid rgba(255, 255, 255, 0.4); 19 | } 20 | 21 | .content h1, 22 | .content h2, 23 | .content h3, 24 | .content h4, 25 | .content h5 { 26 | color: #292e31; 27 | font-weight: normal; 28 | } 29 | 30 | .content { 31 | color: #4c555a; 32 | } 33 | 34 | a { 35 | color: #0094bf; 36 | text-decoration: underline; 37 | } 38 | a:hover { 39 | color: #f1362f; 40 | } 41 | 42 | .menu ul.list li.title { 43 | background: white; 44 | padding-bottom: 5px; 45 | } 46 | 47 | .menu ul.list li:nth-child(2) { 48 | margin-top: 0; 49 | } 50 | 51 | .menu ul.list li:nth-last-child(2) { 52 | background: none; 53 | } 54 | 55 | .menu ul.list li.title a { 56 | padding: 10px 15px; 57 | } 58 | 59 | .menu ul.list li.title a, 60 | .navbar a { 61 | color: #8d9ba8; 62 | text-decoration: none; 63 | font-size: 16px; 64 | font-weight: 300; 65 | } 66 | 67 | .menu ul.list li a { 68 | color: white; 69 | padding: 10px; 70 | font-weight: 300; 71 | text-decoration: none; 72 | } 73 | .menu ul.list li a.active { 74 | color: white; 75 | font-weight: bold; 76 | } 77 | 78 | .copyright { 79 | color: white; 80 | background: #000; 81 | } 82 | 83 | code { 84 | box-sizing: border-box; 85 | display: inline-block; 86 | padding: 0 5px; 87 | background: rgba(0, 148, 191, 0.1); 88 | border-radius: 3px; 89 | color: #0094bf; 90 | font-size: 13px; 91 | line-height: 20px; 92 | } 93 | 94 | pre { 95 | margin: 0; 96 | padding: 12px 12px; 97 | background: rgba(238, 238, 238, 0.35); 98 | border-radius: 3px; 99 | font-size: 13px; 100 | line-height: 1.5em; 101 | font-weight: 500; 102 | } 103 | 104 | .dark body { 105 | color: #fafafa; 106 | } 107 | .dark .content h1, 108 | .dark .content h2, 109 | .dark .content h3, 110 | .dark .content h4, 111 | .dark .content h5 { 112 | color: #fafafa; 113 | } 114 | 115 | .dark code { 116 | background: none; 117 | } 118 | 119 | .dark .content { 120 | color: #fafafa; 121 | } 122 | 123 | .dark .menu ul.list li.title a, 124 | .dark .navbar a { 125 | color: #8d9ba8; 126 | } 127 | 128 | .dark .menu ul.list li a { 129 | color: #fafafa; 130 | } 131 | -------------------------------------------------------------------------------- /oidc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/oidc.png -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/README.md: -------------------------------------------------------------------------------- 1 | # angular-oauth2-oidc-jwks 2 | 3 | `JwksValidationHandler` for `angular-oauth2-odic`. Only needed for implicit flow. 4 | -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/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('karma-coverage-istanbul-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 | coverageIstanbulReporter: { 19 | dir: require('path').join( 20 | __dirname, 21 | '../../coverage/angular-oauth2-oidc-jwks' 22 | ), 23 | reports: ['html', 'lcovonly'], 24 | fixWebpackSourcePaths: true 25 | }, 26 | reporters: ['progress', 'kjhtml'], 27 | port: 9876, 28 | colors: true, 29 | logLevel: config.LOG_INFO, 30 | autoWatch: true, 31 | browsers: ['Chrome'], 32 | singleRun: false, 33 | restartOnFileChange: true 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/angular-oauth2-oidc-jwks", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | }, 7 | "allowedNonPeerDependencies": ["jsrsasign"] 8 | } 9 | -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-oauth2-oidc-jwks", 3 | "license": "MIT", 4 | "author": { 5 | "name": "Manfred Steyer" 6 | }, 7 | "version": "20.0.0", 8 | "repository": "manfredsteyer/angular-oauth2-oidc", 9 | "dependencies": { 10 | "jsrsasign": "^11.0.0", 11 | "tslib": "^2.5.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/src/public-api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/jwks-validation-handler'; 2 | -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/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'; 4 | import 'zone.js/testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting, 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting(), 15 | { 16 | teardown: { destroyAfterEach: false }, 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declaration": true, 6 | "inlineSources": true, 7 | "types": [], 8 | "lib": ["dom", "es2018"] 9 | }, 10 | "angularCompilerOptions": { 11 | "annotateForClosureCompiler": true, 12 | "compilationMode": "partial", 13 | "skipTemplateCodegen": true, 14 | "strictMetadataEmit": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictInjectionParameters": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": ["src/test.ts", "**/*.spec.ts"] 20 | } -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/angular-oauth2-oidc-jwks/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "lib", "camelCase"], 5 | "component-selector": [true, "element", "lib", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/lib/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "lib", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "lib", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": [ 32 | "*.html" 33 | ], 34 | "rules": {} 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /projects/lib/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('karma-coverage-istanbul-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 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/lib/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/lib", 4 | "deleteDestPath": false, 5 | "lib": { 6 | "entryFile": "src/public_api.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /projects/lib/ng-package.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/lib", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | }, 7 | "allowedNonPeerDependencies": [ 8 | "js-sha256", "fast-sha256" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /projects/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-oauth2-oidc", 3 | "version": "20.0.2", 4 | "license": "MIT", 5 | "author": { 6 | "name": "Manfred Steyer" 7 | }, 8 | "description": "Support for OAuth 2(.1) and OpenId Connect (OIDC) in Angular", 9 | "repository": { 10 | "type": "github", 11 | "url": "https://github.com/manfredsteyer/angular-oauth2-oidc" 12 | }, 13 | "peerDependencies": { 14 | "@angular/common": ">=20.0.0", 15 | "@angular/core": ">=20.0.0" 16 | }, 17 | "dependencies": { 18 | "tslib": "^2.5.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /projects/lib/src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.enabledLanguageIds": [ 3 | "asciidoc", 4 | "c", 5 | "cpp", 6 | "csharp", 7 | "css", 8 | "go", 9 | "handlebars", 10 | "html", 11 | "jade", 12 | "javascript", 13 | "javascriptreact", 14 | "json", 15 | "latex", 16 | "less", 17 | "markdown", 18 | "php", 19 | "plaintext", 20 | "pub", 21 | "python", 22 | "restructuredtext", 23 | "rust", 24 | "scss", 25 | "text", 26 | "typescriptreact", 27 | "yml" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /projects/lib/src/angular-oauth-oidc.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { OAuthModuleConfig } from './oauth-module.config'; 5 | import { NullValidationHandler } from './token-validation/null-validation-handler'; 6 | import { provideOAuthClient } from './provider'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule], 10 | declarations: [], 11 | exports: [], 12 | }) 13 | export class OAuthModule { 14 | static forRoot( 15 | config: OAuthModuleConfig = null, 16 | validationHandlerClass = NullValidationHandler 17 | ): ModuleWithProviders { 18 | return { 19 | ngModule: OAuthModule, 20 | providers: [provideOAuthClient(config, validationHandlerClass)], 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /projects/lib/src/base64-helper.ts: -------------------------------------------------------------------------------- 1 | // see: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22 2 | export function b64DecodeUnicode(str) { 3 | const base64 = str.replace(/-/g, '+').replace(/_/g, '/'); 4 | 5 | return decodeURIComponent( 6 | atob(base64) 7 | .split('') 8 | .map(function (c) { 9 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 10 | }) 11 | .join('') 12 | ); 13 | } 14 | 15 | export function base64UrlEncode(str): string { 16 | const base64 = btoa(str); 17 | return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 18 | } 19 | -------------------------------------------------------------------------------- /projects/lib/src/date-time-provider.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | export abstract class DateTimeProvider { 4 | abstract now(): number; 5 | abstract new(): Date; 6 | } 7 | 8 | @Injectable() 9 | export class SystemDateTimeProvider extends DateTimeProvider { 10 | now(): number { 11 | return Date.now(); 12 | } 13 | 14 | new(): Date { 15 | return new Date(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/lib/src/deps.d.ts: -------------------------------------------------------------------------------- 1 | //declare var require: any; 2 | -------------------------------------------------------------------------------- /projects/lib/src/encoder.ts: -------------------------------------------------------------------------------- 1 | import { HttpParameterCodec } from '@angular/common/http'; 2 | /** 3 | * This custom encoder allows charactes like +, % and / to be used in passwords 4 | */ 5 | export class WebHttpUrlEncodingCodec implements HttpParameterCodec { 6 | encodeKey(k: string): string { 7 | return encodeURIComponent(k); 8 | } 9 | 10 | encodeValue(v: string): string { 11 | return encodeURIComponent(v); 12 | } 13 | 14 | decodeKey(k: string): string { 15 | return decodeURIComponent(k); 16 | } 17 | 18 | decodeValue(v: string) { 19 | return decodeURIComponent(v); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /projects/lib/src/events.ts: -------------------------------------------------------------------------------- 1 | export type EventType = 2 | | 'discovery_document_loaded' 3 | | 'jwks_load_error' 4 | | 'invalid_nonce_in_state' 5 | | 'discovery_document_load_error' 6 | | 'discovery_document_validation_error' 7 | | 'user_profile_loaded' 8 | | 'user_profile_load_error' 9 | | 'token_received' 10 | | 'token_error' 11 | | 'code_error' 12 | | 'token_refreshed' 13 | | 'token_refresh_error' 14 | | 'silent_refresh_error' 15 | | 'silently_refreshed' 16 | | 'silent_refresh_timeout' 17 | | 'token_validation_error' 18 | | 'token_expires' 19 | | 'session_changed' 20 | | 'session_error' 21 | | 'session_terminated' 22 | | 'session_unchanged' 23 | | 'logout' 24 | | 'popup_closed' 25 | | 'popup_blocked' 26 | | 'token_revoke_error'; 27 | 28 | export abstract class OAuthEvent { 29 | constructor(readonly type: EventType) {} 30 | } 31 | 32 | export class OAuthSuccessEvent extends OAuthEvent { 33 | constructor(type: EventType, readonly info: any = null) { 34 | super(type); 35 | } 36 | } 37 | 38 | export class OAuthInfoEvent extends OAuthEvent { 39 | constructor(type: EventType, readonly info: any = null) { 40 | super(type); 41 | } 42 | } 43 | 44 | export class OAuthErrorEvent extends OAuthEvent { 45 | constructor( 46 | type: EventType, 47 | readonly reason: object, 48 | readonly params: object = null 49 | ) { 50 | super(type); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /projects/lib/src/factories.ts: -------------------------------------------------------------------------------- 1 | import { MemoryStorage } from './types'; 2 | 3 | export function createDefaultLogger() { 4 | return console; 5 | } 6 | 7 | export function createDefaultStorage() { 8 | return typeof sessionStorage !== 'undefined' 9 | ? sessionStorage 10 | : new MemoryStorage(); 11 | } 12 | -------------------------------------------------------------------------------- /projects/lib/src/interceptors/default-oauth.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Optional } from '@angular/core'; 2 | 3 | import { 4 | HttpEvent, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpRequest, 8 | } from '@angular/common/http'; 9 | import { Observable, of, merge } from 'rxjs'; 10 | import { 11 | catchError, 12 | filter, 13 | map, 14 | take, 15 | mergeMap, 16 | timeout, 17 | } from 'rxjs/operators'; 18 | import { OAuthResourceServerErrorHandler } from './resource-server-error-handler'; 19 | import { OAuthModuleConfig } from '../oauth-module.config'; 20 | import { OAuthService } from '../oauth-service'; 21 | 22 | @Injectable() 23 | export class DefaultOAuthInterceptor implements HttpInterceptor { 24 | constructor( 25 | private oAuthService: OAuthService, 26 | private errorHandler: OAuthResourceServerErrorHandler, 27 | @Optional() private moduleConfig: OAuthModuleConfig 28 | ) {} 29 | 30 | private checkUrl(url: string): boolean { 31 | if (this.moduleConfig.resourceServer.customUrlValidation) { 32 | return this.moduleConfig.resourceServer.customUrlValidation(url); 33 | } 34 | 35 | if (this.moduleConfig.resourceServer.allowedUrls) { 36 | return !!this.moduleConfig.resourceServer.allowedUrls.find((u) => 37 | url.toLowerCase().startsWith(u.toLowerCase()) 38 | ); 39 | } 40 | 41 | return true; 42 | } 43 | 44 | public intercept( 45 | req: HttpRequest, 46 | next: HttpHandler 47 | ): Observable> { 48 | const url = req.url.toLowerCase(); 49 | 50 | if ( 51 | !this.moduleConfig || 52 | !this.moduleConfig.resourceServer || 53 | !this.checkUrl(url) 54 | ) { 55 | return next.handle(req); 56 | } 57 | 58 | const sendAccessToken = this.moduleConfig.resourceServer.sendAccessToken; 59 | 60 | if (!sendAccessToken) { 61 | return next 62 | .handle(req) 63 | .pipe(catchError((err) => this.errorHandler.handleError(err))); 64 | } 65 | 66 | return merge( 67 | of(this.oAuthService.getAccessToken()).pipe(filter((token) => !!token)), 68 | this.oAuthService.events.pipe( 69 | filter((e) => e.type === 'token_received'), 70 | timeout(this.oAuthService.waitForTokenInMsec || 0), 71 | catchError(() => of(null)), // timeout is not an error 72 | map(() => this.oAuthService.getAccessToken()) 73 | ) 74 | ).pipe( 75 | take(1), 76 | mergeMap((token) => { 77 | if (token) { 78 | const header = 'Bearer ' + token; 79 | const headers = req.headers.set('Authorization', header); 80 | req = req.clone({ headers }); 81 | } 82 | 83 | return next 84 | .handle(req) 85 | .pipe(catchError((err) => this.errorHandler.handleError(err))); 86 | }) 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /projects/lib/src/interceptors/resource-server-error-handler.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse } from '@angular/common/http'; 2 | import { Observable, throwError } from 'rxjs'; 3 | 4 | export abstract class OAuthResourceServerErrorHandler { 5 | abstract handleError(err: HttpResponse): Observable; 6 | } 7 | 8 | export class OAuthNoopResourceServerErrorHandler 9 | implements OAuthResourceServerErrorHandler 10 | { 11 | handleError(err: HttpResponse): Observable { 12 | return throwError(err); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/lib/src/oauth-module.config.ts: -------------------------------------------------------------------------------- 1 | export abstract class OAuthModuleConfig { 2 | resourceServer: OAuthResourceServerConfig; 3 | } 4 | 5 | export abstract class OAuthResourceServerConfig { 6 | /** 7 | * Urls for which calls should be intercepted. 8 | * If there is an ResourceServerErrorHandler registered, it is used for them. 9 | * If sendAccessToken is set to true, the access_token is send to them too. 10 | */ 11 | allowedUrls?: Array; 12 | sendAccessToken: boolean; 13 | customUrlValidation?: (url: string) => boolean; 14 | } 15 | -------------------------------------------------------------------------------- /projects/lib/src/provider.ts: -------------------------------------------------------------------------------- 1 | import { makeEnvironmentProviders, EnvironmentProviders } from '@angular/core'; 2 | import { OAuthModuleConfig } from './oauth-module.config'; 3 | import { NullValidationHandler } from './token-validation/null-validation-handler'; 4 | import { DateTimeProvider, SystemDateTimeProvider } from './date-time-provider'; 5 | import { OAuthStorage, OAuthLogger } from './types'; 6 | import { HTTP_INTERCEPTORS } from '@angular/common/http'; 7 | 8 | import { OAuthService } from './oauth-service'; 9 | import { UrlHelperService } from './url-helper.service'; 10 | 11 | import { 12 | OAuthResourceServerErrorHandler, 13 | OAuthNoopResourceServerErrorHandler, 14 | } from './interceptors/resource-server-error-handler'; 15 | import { DefaultOAuthInterceptor } from './interceptors/default-oauth.interceptor'; 16 | import { ValidationHandler } from './token-validation/validation-handler'; 17 | import { createDefaultLogger, createDefaultStorage } from './factories'; 18 | import { 19 | HashHandler, 20 | DefaultHashHandler, 21 | } from './token-validation/hash-handler'; 22 | 23 | export function provideOAuthClient( 24 | config: OAuthModuleConfig = null, 25 | validationHandlerClass = NullValidationHandler 26 | ): EnvironmentProviders { 27 | return makeEnvironmentProviders([ 28 | OAuthService, 29 | UrlHelperService, 30 | { provide: OAuthLogger, useFactory: createDefaultLogger }, 31 | { provide: OAuthStorage, useFactory: createDefaultStorage }, 32 | { provide: ValidationHandler, useClass: validationHandlerClass }, 33 | { provide: HashHandler, useClass: DefaultHashHandler }, 34 | { 35 | provide: OAuthResourceServerErrorHandler, 36 | useClass: OAuthNoopResourceServerErrorHandler, 37 | }, 38 | { provide: OAuthModuleConfig, useValue: config }, 39 | { 40 | provide: HTTP_INTERCEPTORS, 41 | useClass: DefaultOAuthInterceptor, 42 | multi: true, 43 | }, 44 | { provide: DateTimeProvider, useClass: SystemDateTimeProvider }, 45 | ]); 46 | } 47 | -------------------------------------------------------------------------------- /projects/lib/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './angular-oauth-oidc.module'; 2 | export * from './oauth-service'; 3 | // export * from './token-validation/crypto-handler'; 4 | export * from './token-validation/jwks-validation-handler'; 5 | export * from './token-validation/null-validation-handler'; 6 | export * from './token-validation/validation-handler'; 7 | export * from './url-helper.service'; 8 | export * from './auth.config'; 9 | export * from './types'; 10 | export * from './tokens'; 11 | export * from './events'; 12 | export * from './interceptors/default-oauth.interceptor'; 13 | export * from './interceptors/resource-server-error-handler'; 14 | export * from './oauth-module.config'; 15 | export * from './date-time-provider'; 16 | export * from './token-validation/hash-handler'; 17 | export * from './provider'; 18 | -------------------------------------------------------------------------------- /projects/lib/src/token-validation/hash-handler.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { factory } from './js-sha256'; 4 | // const sha256 = factory(); 5 | 6 | import fsha256 from './fast-sha256js'; 7 | 8 | /** 9 | * Abstraction for crypto algorithms 10 | */ 11 | export abstract class HashHandler { 12 | abstract calcHash(valueToHash: string, algorithm: string): Promise; 13 | } 14 | 15 | function decodeUTF8(s) { 16 | if (typeof s !== 'string') throw new TypeError('expected string'); 17 | const d = s, 18 | b = new Uint8Array(d.length); 19 | for (let i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); 20 | return b; 21 | } 22 | 23 | function encodeUTF8(arr) { 24 | const s = []; 25 | for (let i = 0; i < arr.length; i++) s.push(String.fromCharCode(arr[i])); 26 | return s.join(''); 27 | } 28 | 29 | @Injectable() 30 | export class DefaultHashHandler implements HashHandler { 31 | async calcHash(valueToHash: string, algorithm: string): Promise { 32 | // const encoder = new TextEncoder(); 33 | // const hashArray = await window.crypto.subtle.digest(algorithm, data); 34 | // const data = encoder.encode(valueToHash); 35 | 36 | // const fhash = fsha256(valueToHash); 37 | 38 | const candHash = encodeUTF8(fsha256(decodeUTF8(valueToHash))); 39 | 40 | // const hashArray = (sha256 as any).array(valueToHash); 41 | // // const hashString = this.toHashString(hashArray); 42 | // const hashString = this.toHashString2(hashArray); 43 | 44 | // console.debug('hash orig - cand', candHash, hashString); 45 | // alert(1); 46 | 47 | return candHash; 48 | } 49 | 50 | toHashString2(byteArray: number[]) { 51 | let result = ''; 52 | for (const e of byteArray) { 53 | result += String.fromCharCode(e); 54 | } 55 | return result; 56 | } 57 | 58 | toHashString(buffer: ArrayBuffer) { 59 | const byteArray = new Uint8Array(buffer); 60 | let result = ''; 61 | for (const e of byteArray) { 62 | result += String.fromCharCode(e); 63 | } 64 | return result; 65 | } 66 | 67 | // hexString(buffer) { 68 | // const byteArray = new Uint8Array(buffer); 69 | // const hexCodes = [...byteArray].map(value => { 70 | // const hexCode = value.toString(16); 71 | // const paddedHexCode = hexCode.padStart(2, '0'); 72 | // return paddedHexCode; 73 | // }); 74 | 75 | // return hexCodes.join(''); 76 | // } 77 | 78 | // toHashString(hexString: string) { 79 | // let result = ''; 80 | // for (let i = 0; i < hexString.length; i += 2) { 81 | // let hexDigit = hexString.charAt(i) + hexString.charAt(i + 1); 82 | // let num = parseInt(hexDigit, 16); 83 | // result += String.fromCharCode(num); 84 | // } 85 | // return result; 86 | // } 87 | } 88 | -------------------------------------------------------------------------------- /projects/lib/src/token-validation/jwks-validation-handler.ts: -------------------------------------------------------------------------------- 1 | import { NullValidationHandler } from './null-validation-handler'; 2 | 3 | const err = `PLEASE READ THIS CAREFULLY: 4 | 5 | Beginning with angular-oauth2-oidc version 9, the JwksValidationHandler 6 | has been moved to an library of its own. If you need it for implementing 7 | OAuth2/OIDC **implicit flow**, please install it using npm: 8 | 9 | npm i angular-oauth2-oidc-jwks --save 10 | 11 | After that, you can import it into your application: 12 | 13 | import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks'; 14 | 15 | Please note, that this dependency is not needed for the **code flow**, 16 | which is nowadays the **recommented** one for single page applications. 17 | This also results in smaller bundle sizes. 18 | `; 19 | 20 | /** 21 | * This is just a dummy of the JwksValidationHandler 22 | * telling the users that the real one has been moved 23 | * to an library of its own, namely angular-oauth2-oidc-utils 24 | */ 25 | export class JwksValidationHandler extends NullValidationHandler { 26 | constructor() { 27 | super(); 28 | console.error(err); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/lib/src/token-validation/null-validation-handler.ts: -------------------------------------------------------------------------------- 1 | import { ValidationHandler, ValidationParams } from './validation-handler'; 2 | 3 | /** 4 | * A validation handler that isn't validating nothing. 5 | * Can be used to skip validation (at your own risk). 6 | */ 7 | export class NullValidationHandler implements ValidationHandler { 8 | validateSignature(validationParams: ValidationParams): Promise { 9 | return Promise.resolve(null); 10 | } 11 | validateAtHash(validationParams: ValidationParams): Promise { 12 | return Promise.resolve(true); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/lib/src/token-validation/validation-handler.ts: -------------------------------------------------------------------------------- 1 | import { base64UrlEncode } from '../base64-helper'; 2 | 3 | export interface ValidationParams { 4 | idToken: string; 5 | accessToken: string; 6 | idTokenHeader: object; 7 | idTokenClaims: object; 8 | jwks: object; 9 | loadKeys: () => Promise; 10 | } 11 | 12 | /** 13 | * Interface for Handlers that are hooked in to 14 | * validate tokens. 15 | */ 16 | export abstract class ValidationHandler { 17 | /** 18 | * Validates the signature of an id_token. 19 | */ 20 | public abstract validateSignature( 21 | validationParams: ValidationParams 22 | ): Promise; 23 | 24 | /** 25 | * Validates the at_hash in an id_token against the received access_token. 26 | */ 27 | public abstract validateAtHash( 28 | validationParams: ValidationParams 29 | ): Promise; 30 | } 31 | 32 | /** 33 | * This abstract implementation of ValidationHandler already implements 34 | * the method validateAtHash. However, to make use of it, 35 | * you have to override the method calcHash. 36 | */ 37 | export abstract class AbstractValidationHandler implements ValidationHandler { 38 | /** 39 | * Validates the signature of an id_token. 40 | */ 41 | abstract validateSignature(validationParams: ValidationParams): Promise; 42 | 43 | /** 44 | * Validates the at_hash in an id_token against the received access_token. 45 | */ 46 | async validateAtHash(params: ValidationParams): Promise { 47 | const hashAlg = this.inferHashAlgorithm(params.idTokenHeader); 48 | 49 | const tokenHash = await this.calcHash(params.accessToken, hashAlg); // sha256(accessToken, { asString: true }); 50 | 51 | const leftMostHalf = tokenHash.substr(0, tokenHash.length / 2); 52 | 53 | const atHash = base64UrlEncode(leftMostHalf); 54 | 55 | const claimsAtHash = params.idTokenClaims['at_hash'].replace(/=/g, ''); 56 | 57 | if (atHash !== claimsAtHash) { 58 | console.error('exptected at_hash: ' + atHash); 59 | console.error('actual at_hash: ' + claimsAtHash); 60 | } 61 | 62 | return atHash === claimsAtHash; 63 | } 64 | 65 | /** 66 | * Infers the name of the hash algorithm to use 67 | * from the alg field of an id_token. 68 | * 69 | * @param jwtHeader the id_token's parsed header 70 | */ 71 | protected inferHashAlgorithm(jwtHeader: object): string { 72 | const alg: string = jwtHeader['alg']; 73 | 74 | if (!alg.match(/^.S[0-9]{3}$/)) { 75 | throw new Error('Algorithm not supported: ' + alg); 76 | } 77 | 78 | return 'sha-' + alg.substr(2); 79 | } 80 | 81 | /** 82 | * Calculates the hash for the passed value by using 83 | * the passed hash algorithm. 84 | * 85 | * @param valueToHash 86 | * @param algorithm 87 | */ 88 | protected abstract calcHash( 89 | valueToHash: string, 90 | algorithm: string 91 | ): Promise; 92 | } 93 | -------------------------------------------------------------------------------- /projects/lib/src/tokens.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { AuthConfig } from './auth.config'; 3 | 4 | export const AUTH_CONFIG = new InjectionToken('AUTH_CONFIG'); 5 | -------------------------------------------------------------------------------- /projects/lib/src/url-helper.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class UrlHelperService { 5 | public getHashFragmentParams(customHashFragment?: string): object { 6 | let hash = customHashFragment || window.location.hash; 7 | 8 | hash = decodeURIComponent(hash); 9 | 10 | if (hash.indexOf('#') !== 0) { 11 | return {}; 12 | } 13 | 14 | const questionMarkPosition = hash.indexOf('?'); 15 | 16 | if (questionMarkPosition > -1) { 17 | hash = hash.substr(questionMarkPosition + 1); 18 | } else { 19 | hash = hash.substr(1); 20 | } 21 | 22 | return this.parseQueryString(hash); 23 | } 24 | 25 | public parseQueryString(queryString: string): object { 26 | const data = {}; 27 | let pair, separatorIndex, escapedKey, escapedValue, key, value; 28 | 29 | if (queryString === null) { 30 | return data; 31 | } 32 | 33 | const pairs = queryString.split('&'); 34 | 35 | for (let i = 0; i < pairs.length; i++) { 36 | pair = pairs[i]; 37 | separatorIndex = pair.indexOf('='); 38 | 39 | if (separatorIndex === -1) { 40 | escapedKey = pair; 41 | escapedValue = null; 42 | } else { 43 | escapedKey = pair.substr(0, separatorIndex); 44 | escapedValue = pair.substr(separatorIndex + 1); 45 | } 46 | 47 | key = decodeURIComponent(escapedKey); 48 | value = decodeURIComponent(escapedValue); 49 | 50 | if (key.substr(0, 1) === '/') { 51 | key = key.substr(1); 52 | } 53 | 54 | data[key] = value; 55 | } 56 | 57 | return data; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /projects/lib/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declarationMap": true, 6 | "module": "es2015", 7 | "moduleResolution": "bundler", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "types": [], 14 | "lib": [ 15 | "dom", 16 | "es2015" 17 | ] 18 | }, 19 | "exclude": ["src/test.ts", "**/*.spec.ts"], 20 | "angularCompilerOptions": { 21 | "compilationMode": "partial" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /projects/lib/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false, 5 | "outDir": "../../out-tsc/lib-prod" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/lib/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/quickstart-demo/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /projects/quickstart-demo/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('karma-coverage-istanbul-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 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/quickstart-demo'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/quickstart-demo/src/app/app.component.css -------------------------------------------------------------------------------- /projects/quickstart-demo/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | @if (idToken) { 3 |
4 |
5 |

Welcome to {{ title }}!

6 |
7 |

User

8 |

{{ userName }}

9 |

Id-Token

10 |

{{ idToken }}

11 |

Access Token

12 |

{{ accessToken }}

13 |

14 | 15 |

16 |
17 | } 18 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(waitForAsync(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [AppComponent], 8 | }).compileComponents(); 9 | })); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.debugElement.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have as title 'quickstart-demo'`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app.title).toEqual('quickstart-demo'); 21 | }); 22 | 23 | it('should render title in a h1 tag', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.debugElement.nativeElement; 27 | expect(compiled.querySelector('h1').textContent).toContain( 28 | 'Welcome to quickstart-demo!' 29 | ); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { OAuthService } from 'angular-oauth2-oidc'; 3 | import { filter } from 'rxjs/operators'; 4 | import { authCodeFlowConfig } from './auth.config'; 5 | 6 | @Component({ 7 | selector: 'app-root', 8 | templateUrl: './app.component.html', 9 | styleUrls: ['./app.component.css'], 10 | standalone: false, 11 | }) 12 | export class AppComponent { 13 | title = 'Quickstart Demo'; 14 | 15 | constructor(private oauthService: OAuthService) { 16 | this.oauthService.configure(authCodeFlowConfig); 17 | this.oauthService.loadDiscoveryDocumentAndLogin(); 18 | 19 | //this.oauthService.setupAutomaticSilentRefresh(); 20 | 21 | // Automatically load user profile 22 | this.oauthService.events 23 | .pipe(filter((e) => e.type === 'token_received')) 24 | .subscribe((_) => this.oauthService.loadUserProfile()); 25 | } 26 | 27 | get userName(): string { 28 | const claims = this.oauthService.getIdentityClaims(); 29 | if (!claims) return null; 30 | return claims['given_name']; 31 | } 32 | 33 | get idToken(): string { 34 | return this.oauthService.getIdToken(); 35 | } 36 | 37 | get accessToken(): string { 38 | return this.oauthService.getAccessToken(); 39 | } 40 | 41 | refresh() { 42 | this.oauthService.refreshToken(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { OAuthModule } from 'angular-oauth2-oidc'; 6 | import { 7 | provideHttpClient, 8 | withInterceptorsFromDi, 9 | } from '@angular/common/http'; 10 | 11 | @NgModule({ 12 | declarations: [AppComponent], 13 | bootstrap: [AppComponent], 14 | imports: [BrowserModule, OAuthModule.forRoot()], 15 | providers: [provideHttpClient(withInterceptorsFromDi())], 16 | }) 17 | export class AppModule {} 18 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/app/auth.config.ts: -------------------------------------------------------------------------------- 1 | import { AuthConfig } from 'angular-oauth2-oidc'; 2 | 3 | export const authCodeFlowConfig: AuthConfig = { 4 | issuer: 'https://idsvr4.azurewebsites.net', 5 | redirectUri: window.location.origin + '/index.html', 6 | clientId: 'spa', 7 | responseType: 'code', 8 | scope: 'openid profile email offline_access api', 9 | showDebugInformation: true, 10 | timeoutFactor: 0.01, 11 | checkOrigin: false, 12 | }; 13 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/quickstart-demo/src/assets/.gitkeep -------------------------------------------------------------------------------- /projects/quickstart-demo/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /projects/quickstart-demo/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 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/quickstart-demo/src/favicon.ico -------------------------------------------------------------------------------- /projects/quickstart-demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QuickstartDemo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /projects/quickstart-demo/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() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /projects/quickstart-demo/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 | * APPLICATION IMPORTS 52 | */ 53 | -------------------------------------------------------------------------------- /projects/quickstart-demo/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /projects/quickstart-demo/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 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting(), 14 | { 15 | teardown: { destroyAfterEach: false }, 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /projects/quickstart-demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/test.ts", "src/**/*.spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/quickstart-demo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts", "src/polyfills.ts"], 8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/quickstart-standalone/src/app/app.component.css -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | @if (idToken) { 2 |
3 |
4 |

Welcome to {{ title }}!

5 |
6 |

User

7 |

{{ userName }}

8 |

Id-Token

9 |

{{ idToken }}

10 |

Access Token

11 |

{{ accessToken }}

12 |

13 | 14 |

15 |
16 | } 17 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | declarations: [AppComponent], 8 | }).compileComponents(); 9 | }); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have as title 'quickstart-standalone'`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app.title).toEqual('quickstart-standalone'); 21 | }); 22 | 23 | it('should render title', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.nativeElement as HTMLElement; 27 | expect(compiled.querySelector('.content span')?.textContent).toContain( 28 | 'quickstart-standalone app is running!' 29 | ); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { OAuthService } from 'angular-oauth2-oidc'; 3 | import { filter } from 'rxjs/operators'; 4 | import { authCodeFlowConfig } from './auth.config'; 5 | 6 | @Component({ 7 | selector: 'app-root', 8 | templateUrl: './app.component.html', 9 | styleUrls: ['./app.component.css'], 10 | imports: [], 11 | }) 12 | export class AppComponent { 13 | title = 'Quickstart Demo'; 14 | 15 | constructor(private oauthService: OAuthService) { 16 | this.oauthService.configure(authCodeFlowConfig); 17 | this.oauthService.loadDiscoveryDocumentAndLogin(); 18 | 19 | //this.oauthService.setupAutomaticSilentRefresh(); 20 | 21 | // Automatically load user profile 22 | this.oauthService.events 23 | .pipe(filter((e) => e.type === 'token_received')) 24 | .subscribe((_) => this.oauthService.loadUserProfile()); 25 | } 26 | 27 | get userName(): string { 28 | const claims = this.oauthService.getIdentityClaims(); 29 | if (!claims) return null; 30 | return claims['given_name']; 31 | } 32 | 33 | get idToken(): string { 34 | return this.oauthService.getIdToken(); 35 | } 36 | 37 | get accessToken(): string { 38 | return this.oauthService.getAccessToken(); 39 | } 40 | 41 | refresh() { 42 | this.oauthService.refreshToken(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppComponent } from './app.component'; 5 | 6 | @NgModule({ 7 | declarations: [AppComponent], 8 | imports: [BrowserModule], 9 | providers: [], 10 | bootstrap: [AppComponent], 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/app/auth.config.ts: -------------------------------------------------------------------------------- 1 | import { AuthConfig } from 'angular-oauth2-oidc'; 2 | 3 | export const authCodeFlowConfig: AuthConfig = { 4 | issuer: 'https://idsvr4.azurewebsites.net', 5 | redirectUri: window.location.origin + '/index.html', 6 | clientId: 'spa', 7 | responseType: 'code', 8 | scope: 'openid profile email offline_access api', 9 | showDebugInformation: true, 10 | timeoutFactor: 0.01, 11 | }; 12 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/quickstart-standalone/src/assets/.gitkeep -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/quickstart-standalone/src/favicon.ico -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QuickstartStandalone 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | 3 | import { provideHttpClient } from '@angular/common/http'; 4 | 5 | import { AppComponent } from './app/app.component'; 6 | import { provideOAuthClient } from 'angular-oauth2-oidc'; 7 | 8 | bootstrapApplication(AppComponent, { 9 | providers: [provideHttpClient(), provideOAuthClient()], 10 | }); 11 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/quickstart-standalone/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/sample/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('karma-coverage-istanbul-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 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/sample/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/sample/src/app/app.component.css -------------------------------------------------------------------------------- /projects/sample/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /projects/sample/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { AppComponent } from './app.component'; 4 | import { AppModule } from './app.module'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(waitForAsync(() => { 8 | TestBed.configureTestingModule({ 9 | imports: [AppModule], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', waitForAsync(() => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | })); 18 | 19 | it('should render link to flight in a a tag', waitForAsync(() => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | fixture.detectChanges(); 22 | const compiled = fixture.debugElement.nativeElement; 23 | console.log(compiled); 24 | expect(compiled.querySelectorAll(' li a')[2].textContent).toContain( 25 | 'Book a Flight' 26 | ); 27 | })); 28 | }); 29 | -------------------------------------------------------------------------------- /projects/sample/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { OAuthModule } from 'angular-oauth2-oidc'; 5 | import { 6 | provideHttpClient, 7 | withInterceptorsFromDi, 8 | } from '@angular/common/http'; 9 | 10 | import { AppComponent } from './app.component'; 11 | import { APP_ROUTES } from './app.routes'; 12 | import { BASE_URL } from './app.tokens'; 13 | import { FlightHistoryComponent } from './flight-history/flight-history.component'; 14 | import { HomeComponent } from './home/home.component'; 15 | import { PasswordFlowLoginComponent } from './password-flow-login/password-flow-login.component'; 16 | // import { CustomDateTimeProvider } from './shared/date/custom-date-time-provider'; 17 | import { SharedModule } from './shared/shared.module'; 18 | import { RouterModule } from '@angular/router'; 19 | import { useHash } from '../flags'; 20 | 21 | @NgModule({ 22 | declarations: [ 23 | AppComponent, 24 | HomeComponent, 25 | FlightHistoryComponent, 26 | PasswordFlowLoginComponent, 27 | ], 28 | bootstrap: [AppComponent], 29 | imports: [ 30 | BrowserModule, 31 | RouterModule.forRoot(APP_ROUTES, { useHash }), 32 | FormsModule, 33 | ReactiveFormsModule, 34 | SharedModule.forRoot(), 35 | OAuthModule.forRoot({ 36 | resourceServer: { 37 | allowedUrls: ['http://www.angular.at/api'], 38 | sendAccessToken: true, 39 | }, 40 | }), 41 | ], 42 | providers: [ 43 | // (useHash) ? { provide: LocationStrategy, useClass: HashLocationStrategy } : [], 44 | // {provide: AuthConfig, useValue: authConfig }, 45 | // { provide: OAuthStorage, useValue: localStorage }, 46 | // { provide: ValidationHandler, useClass: JwksValidationHandler }, 47 | // Enabled the custom date time provider will make the sample fail to login, since the demo Idp time is correctly synced to the world time. 48 | // { provide: DateTimeProvider, useClass: CustomDateTimeProvider }, 49 | { provide: BASE_URL, useValue: 'http://www.angular.at' }, 50 | provideHttpClient(withInterceptorsFromDi()), 51 | ], 52 | }) 53 | export class AppModule {} 54 | -------------------------------------------------------------------------------- /projects/sample/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { PasswordFlowLoginComponent } from './password-flow-login/password-flow-login.component'; 2 | import { Routes } from '@angular/router'; 3 | import { HomeComponent } from './home/home.component'; 4 | import { FlightHistoryComponent } from './flight-history/flight-history.component'; 5 | 6 | export let APP_ROUTES: Routes = [ 7 | { 8 | path: '', 9 | redirectTo: 'home', 10 | pathMatch: 'full', 11 | }, 12 | { 13 | path: 'home', 14 | component: HomeComponent, 15 | }, 16 | { 17 | path: 'password-flow-login', 18 | component: PasswordFlowLoginComponent, 19 | }, 20 | { 21 | path: 'flight-booking', 22 | loadChildren: () => 23 | import('./flight-booking/flight-booking.module').then( 24 | (mod) => mod.FlightBookingModule 25 | ), 26 | }, 27 | { 28 | path: 'history', 29 | component: FlightHistoryComponent, 30 | outlet: 'aux', 31 | }, 32 | { 33 | path: '**', 34 | redirectTo: 'home', 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /projects/sample/src/app/app.tokens.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | export const BASE_URL = new InjectionToken('BASE_URL'); 4 | -------------------------------------------------------------------------------- /projects/sample/src/app/auth-code-flow.config.ts: -------------------------------------------------------------------------------- 1 | import { AuthConfig } from 'angular-oauth2-oidc'; 2 | import { useSilentRefreshForCodeFlow } from '../flags'; 3 | 4 | export const authCodeFlowConfig: AuthConfig = { 5 | issuer: 'https://idsvr4.azurewebsites.net', 6 | 7 | // URL of the SPA to redirect the user to after login 8 | redirectUri: 9 | window.location.origin + 10 | (localStorage.getItem('useHashLocationStrategy') === 'true' 11 | ? '/#/index.html' 12 | : '/index.html'), 13 | 14 | // The SPA's id. The SPA is registerd with this id at the auth-server 15 | // clientId: 'server.code', 16 | clientId: 'spa', 17 | 18 | // Just needed if your auth server demands a secret. In general, this 19 | // is a sign that the auth server is not configured with SPAs in mind 20 | // and it might not enforce further best practices vital for security 21 | // such applications. 22 | // dummyClientSecret: 'secret', 23 | 24 | responseType: 'code', 25 | 26 | // set the scope for the permissions the client should request 27 | // The first four are defined by OIDC. 28 | // Important: Request offline_access to get a refresh token 29 | // The api scope is a usecase specific one 30 | scope: useSilentRefreshForCodeFlow 31 | ? 'openid profile email api' 32 | : 'openid profile email offline_access api', 33 | 34 | // ^^ Please note that offline_access is not needed for silent refresh 35 | // At least when using idsvr, this even prevents silent refresh 36 | // as idsvr ALWAYS prompts the user for consent when this scope is 37 | // requested 38 | 39 | // This is needed for silent refresh (refreshing tokens w/o a refresh_token) 40 | // **AND** for logging in with a popup 41 | silentRefreshRedirectUri: `${window.location.origin}/silent-refresh.html`, 42 | 43 | useSilentRefresh: useSilentRefreshForCodeFlow, 44 | 45 | showDebugInformation: true, 46 | 47 | sessionChecksEnabled: false, 48 | 49 | timeoutFactor: 0.01, 50 | // disablePKCI: true, 51 | 52 | clearHashAfterLogin: true, 53 | }; 54 | -------------------------------------------------------------------------------- /projects/sample/src/app/auth-no-discovery.config.ts: -------------------------------------------------------------------------------- 1 | import { AuthConfig } from 'angular-oauth2-oidc'; 2 | 3 | export const noDiscoveryAuthConfig: AuthConfig = { 4 | clientId: 5 | '1004270452653-m396kcs7jc3970turlp7ffh6bv4t1b86.apps.googleusercontent.com', 6 | redirectUri: 'http://localhost:4200/index.html', 7 | postLogoutRedirectUri: '', 8 | loginUrl: 'https://accounts.google.com/o/oauth2/v2/auth', 9 | scope: 'openid profile email', 10 | resource: '', 11 | rngUrl: '', 12 | oidc: true, 13 | requestAccessToken: true, 14 | options: null, 15 | issuer: 'https://accounts.google.com', 16 | clearHashAfterLogin: true, 17 | tokenEndpoint: 'https://www.googleapis.com/oauth2/v4/token', 18 | userinfoEndpoint: 'https://www.googleapis.com/oauth2/v3/userinfo', 19 | responseType: 'token', 20 | showDebugInformation: true, 21 | silentRefreshRedirectUri: 'http://localhost:4200/silent-refresh.html', 22 | silentRefreshMessagePrefix: '', 23 | silentRefreshShowIFrame: false, 24 | silentRefreshTimeout: 20000, 25 | dummyClientSecret: null, 26 | requireHttps: 'remoteOnly', 27 | strictDiscoveryDocumentValidation: false, 28 | jwks: { 29 | keys: [ 30 | { 31 | kty: 'RSA', 32 | alg: 'RS256', 33 | use: 'sig', 34 | kid: '7540561fdb04b89d824a1b7b9e8849873e7cb50e', 35 | n: 36 | // tslint:disable-next-line: max-line-length 37 | 'sSFZrLIrXzvXBCehdPR10T-mfHWFU5ZtGzW9buI7wT_tJzZ1SRUc2l1NH92kGV9bmWRtDLjWcWFwMG7rbjX25-R-62lD1k15gQiO4bhx7gbV05e36os2vXTs0ypj9GS9y8X_2fYAnxxulMLwz4m24Ejo2tQI43-V-3Tec6cSXe0FjhRaPbGdS8GHPDKkhpJ1NHMZ38vhddIImOfvtVuz3lt_zwjBsAC6Q7PHs2GOm3KtC22DCwXMYSri4QOQcasuvTlZxIQSIksTyuH0T02IH5SJvQZSx46Vfq8BM4JP-zEEjzadoyxQPouRM6TrUeaqNv5B1f1lbH6G0G_r_ddYWQ', 38 | e: 'AQAB', 39 | }, 40 | { 41 | kty: 'RSA', 42 | alg: 'RS256', 43 | use: 'sig', 44 | kid: '778233e8f6f342ea09e867aad25f543adeebf372', 45 | n: 46 | // tslint:disable-next-line: max-line-length 47 | '8MMxQ9F7R1zJ57QvLX-HqUlTVLLofCzZ3-lxohJr8ivJDGZoCqll7ZTNO0nGMgnPpIO-3BQLkaNGQDCpnID1vNIjClFFl0E3cN5bDX15uxCQeQDsm25fTlphpy5FkdoHCviswtrsl2KKUPeRlKqCqMjlDO27KuxIwzIPdNSqv4tseZmI-biFt2JlO9htgODrVqaawdm27t9HcWfOK_a5czRFDHWck2-ZwjbCOF9CtF1ggYm11aV0TElExXr5fgjAQdZ1yGmJvir127BRUgyIy5cpyf7VRRf2Cv7whSMoVJr4W3OK0H9vkuFLnlBiBNYQmH_eWy5U4jBfZjBqvA7Oww', 48 | e: 'AQAB', 49 | }, 50 | { 51 | kty: 'RSA', 52 | alg: 'RS256', 53 | use: 'sig', 54 | kid: '8ec17994394464d95b0b3d906326f1cdde8aee64', 55 | n: 56 | // tslint:disable-next-line: max-line-length 57 | 'w49KfvzGWVXH4vyUxvP29_QTmJfvLp4RPT1WlI6Wo2aNvn6j9vRSLDrK2CnOvvrrlUKvR-8FTcyNi9pRKXDwDhEJcyVFBJVi4PqDh0KIX_dOGYCulr5FUvU0HXQxlMWSHIsJjfGbMMUwM0p09y8KHL-kipiipzn80EpBmrI4Q3t6XOAZJSmbIPaGZJDjyoWWV0TDdVDBMfkqII6tOOB7Ha189AZjz7FHYXR9CIc0Jm6rFy0tVpdHFEG3ptcNQEDQ5ghyMM4PDM4ZmQ5uk3WgHVqnpdmGEfKekLwmYFWgnI-ux_MabltIxr9TE1qubEmebM64rOusHBF0mSbEwggbyw', 58 | e: 'AQAB', 59 | }, 60 | ], 61 | }, 62 | customQueryParams: null, 63 | silentRefreshIFrameName: 'angular-oauth-oidc-silent-refresh-iframe', 64 | timeoutFactor: 0.75, 65 | sessionCheckIntervall: 3000, 66 | sessionCheckIFrameName: 'angular-oauth-oidc-check-session-iframe', 67 | disableAtHashCheck: false, 68 | skipSubjectCheck: false, 69 | }; 70 | -------------------------------------------------------------------------------- /projects/sample/src/app/auth-password-flow.config.ts: -------------------------------------------------------------------------------- 1 | // This api will come in the next version 2 | 3 | import { AuthConfig } from 'angular-oauth2-oidc'; 4 | 5 | export const authPasswordFlowConfig: AuthConfig = { 6 | // Url of the Identity Provider 7 | issuer: 'https://steyer-identity-server.azurewebsites.net/identity', 8 | 9 | // URL of the SPA to redirect the user to after login 10 | redirectUri: window.location.origin + '/index.html', 11 | 12 | // URL of the SPA to redirect the user after silent refresh 13 | silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', 14 | 15 | // The SPA's id. The SPA is registerd with this id at the auth-server 16 | clientId: 'demo-resource-owner', 17 | 18 | dummyClientSecret: 'geheim', 19 | 20 | // set the scope for the permissions the client should request 21 | // The first three are defined by OIDC. The 4th is a usecase-specific one 22 | scope: 'openid profile email voucher', 23 | 24 | showDebugInformation: true, 25 | 26 | oidc: false, 27 | }; 28 | -------------------------------------------------------------------------------- /projects/sample/src/app/auth.config.ts: -------------------------------------------------------------------------------- 1 | // This api will come in the next version 2 | 3 | import { AuthConfig } from 'angular-oauth2-oidc'; 4 | 5 | export const authConfig: AuthConfig = { 6 | // Url of the Identity Provider 7 | issuer: 'https://idsvr4.azurewebsites.net', 8 | 9 | // URL of the SPA to redirect the user to after login 10 | // redirectUri: window.location.origin 11 | // + ((localStorage.getItem('useHashLocationStrategy') === 'true') 12 | // ? '/#/index.html' 13 | // : '/index.html'), 14 | 15 | redirectUri: window.location.origin + '/index.html', 16 | 17 | // URL of the SPA to redirect the user after silent refresh 18 | silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', 19 | 20 | // The SPA's id. The SPA is registerd with this id at the auth-server 21 | clientId: 'implicit', 22 | 23 | // set the scope for the permissions the client should request 24 | // The first three are defined by OIDC. The 4th is a usecase-specific one 25 | scope: 'openid profile email api', 26 | 27 | // silentRefreshShowIFrame: true, 28 | 29 | showDebugInformation: true, 30 | 31 | sessionChecksEnabled: true, 32 | 33 | checkOrigin: true, 34 | 35 | timeoutFactor: 0.01, 36 | }; 37 | -------------------------------------------------------------------------------- /projects/sample/src/app/auth.google.config.ts: -------------------------------------------------------------------------------- 1 | // This api will come in the next version 2 | 3 | import { AuthConfig } from 'angular-oauth2-oidc'; 4 | 5 | export const googleAuthConfig: AuthConfig = { 6 | // Url of the Identity Provider 7 | issuer: 'https://accounts.google.com', 8 | 9 | // URL of the SPA to redirect the user to after login 10 | redirectUri: window.location.origin + '/index.html', 11 | 12 | // URL of the SPA to redirect the user after silent refresh 13 | silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', 14 | 15 | // The SPA's id. The SPA is registerd with this id at the auth-server 16 | clientId: 17 | '1004270452653-m396kcs7jc3970turlp7ffh6bv4t1b86.apps.googleusercontent.com', 18 | 19 | strictDiscoveryDocumentValidation: false, 20 | 21 | // set the scope for the permissions the client should request 22 | // The first three are defined by OIDC. The 4th is a usecase-specific one 23 | scope: 'openid profile email', 24 | 25 | showDebugInformation: true, 26 | 27 | sessionChecksEnabled: true, 28 | }; 29 | -------------------------------------------------------------------------------- /projects/sample/src/app/entities/flight.ts: -------------------------------------------------------------------------------- 1 | export interface Flight { 2 | id: number; // int + double 3 | from: string; 4 | to: string; 5 | date: string; 6 | // JSON: ISO-String 2016-12-24T17:00:00.000+01:00 7 | } 8 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/alt-flight-card/alt-flight-card.component.html: -------------------------------------------------------------------------------- 1 |
5 |

{{ item.from }} - {{ item.to }}

6 |

Flugnr. #{{ item.id }}

7 |

Datum: {{ item.date | date: 'dd.MM.yyyy HH:mm' }}

8 | 9 |

10 | 16 |

17 |
18 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/alt-flight-card/alt-flight.card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { Flight } from '../../entities/flight'; 3 | 4 | @Component({ 5 | selector: 'alt-flight-card', 6 | templateUrl: 'alt-flight-card.component.html', 7 | standalone: false, 8 | }) 9 | export class AltFlightCardComponent { 10 | @Input() item: Flight; 11 | @Input() selected: boolean; 12 | @Output() selectedChange = new EventEmitter(); 13 | 14 | select() { 15 | this.selectedChange.emit(true); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/alt-flight-card/flight-list.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { Flight } from '../../entities/flight'; 3 | 4 | @Component({ 5 | selector: 'flight-list', 6 | template: ` 7 |
8 | @for (f of flights; track f) { 9 |
10 | 15 | 16 |
17 | } 18 |
19 | `, 20 | standalone: false, 21 | }) 22 | export class FlightListComponent { 23 | @Input() flights: Flight[] = []; 24 | @Input() selectedFlight: Flight; 25 | @Output() selectedFlightChange = new EventEmitter(); 26 | 27 | change(f: Flight) { 28 | this.selectedFlightChange.emit(f); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-booking.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-booking.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'flight-booking', 5 | templateUrl: './flight-booking.component.html', 6 | standalone: false, 7 | }) 8 | export class FlightBookingComponent {} 9 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-booking.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FlightSearchComponent } from './flight-search/flight-search.component'; 3 | import { FlightCardComponent } from './flight-card/flight.card.component'; 4 | import { AltFlightCardComponent } from './alt-flight-card/alt-flight.card.component'; 5 | import { FlightListComponent } from './alt-flight-card/flight-list'; 6 | import { PassengerSearchComponent } from './passenger-search/passenger-search.component'; 7 | import { FlightEditComponent } from './flight-edit/flight-edit.component'; 8 | import { FlightSearchReactiveComponent } from './flight-search-reactive/flight-search-reactive.component'; 9 | import { CommonModule } from '@angular/common'; 10 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 11 | import { SharedModule } from '../shared/shared.module'; 12 | import { FlightBookingRouterModule } from './flight-booking.routes'; 13 | import { FlightBookingComponent } from './flight-booking.component'; 14 | import { FlightService } from './services/flight.service'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, // ngFor 19 | FormsModule, 20 | ReactiveFormsModule, 21 | SharedModule, 22 | FlightBookingRouterModule, 23 | ], 24 | declarations: [ 25 | FlightSearchComponent, 26 | FlightCardComponent, 27 | AltFlightCardComponent, 28 | FlightListComponent, 29 | FlightSearchReactiveComponent, 30 | PassengerSearchComponent, 31 | FlightEditComponent, 32 | FlightBookingComponent, 33 | ], 34 | providers: [FlightService], 35 | exports: [], 36 | }) 37 | export class FlightBookingModule {} 38 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-booking.routes.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { FlightSearchComponent } from './flight-search/flight-search.component'; 4 | import { PassengerSearchComponent } from './passenger-search/passenger-search.component'; 5 | import { FlightEditComponent } from './flight-edit/flight-edit.component'; 6 | import { FlightBookingComponent } from './flight-booking.component'; 7 | import { AuthGuard } from '../shared/auth/auth.guard'; 8 | 9 | let FLIGHT_BOOKING_ROUTES: Routes = [ 10 | { 11 | path: '', 12 | component: FlightBookingComponent, 13 | canActivate: [() => inject(AuthGuard).canActivate()], 14 | children: [ 15 | { 16 | path: 'flight-search', 17 | component: FlightSearchComponent, 18 | }, 19 | { 20 | path: 'passenger-search', 21 | component: PassengerSearchComponent, 22 | }, 23 | { 24 | path: 'flight-edit/:id', 25 | component: FlightEditComponent, 26 | canDeactivate: [ 27 | (component: FlightEditComponent) => component.canDeactivate(), 28 | ], 29 | }, 30 | ], 31 | }, 32 | ]; 33 | 34 | export let FlightBookingRouterModule = RouterModule.forChild( 35 | FLIGHT_BOOKING_ROUTES 36 | ); 37 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-card/flight-card.component.html: -------------------------------------------------------------------------------- 1 |
7 |

{{ item.from }} - {{ item.to }}

8 |

Flugnr. #{{ item.id }}

9 |

Datum: {{ item.date | date: 'dd.MM.yyyy HH:mm' }}

10 | 11 |

12 | 18 | 19 | 20 |

21 |
22 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-card/flight.card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { Flight } from '../../entities/flight'; 3 | 4 | @Component({ 5 | selector: 'flight-card', 6 | templateUrl: './flight-card.component.html', 7 | standalone: false, 8 | }) 9 | export class FlightCardComponent { 10 | @Input() item: Flight; 11 | @Input() selectedItem: Flight; 12 | @Output() selectedItemChange = new EventEmitter(); 13 | 14 | select() { 15 | this.selectedItemChange.emit(this.item); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-edit/flight-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | @Component({ 5 | template: ` 6 |

Flight Edit!

7 |

Hier könnte auch der Datensatz mit der Id {{ id }} stehen!

8 | 9 | @if (exitWarning.show) { 10 |
11 |
Daten wurden nicht gespeichert! Trotzdem Maske verlassen?
12 |
13 | Ja 19 | Nein 25 |
26 |
27 | } 28 | `, 29 | standalone: false, 30 | }) 31 | export class FlightEditComponent implements OnInit { 32 | public id: string; 33 | 34 | constructor(private route: ActivatedRoute) { 35 | route.params.subscribe((p) => { 36 | this.id = p['id']; 37 | }); 38 | } 39 | 40 | ngOnInit() {} 41 | 42 | exitWarning = { 43 | show: false, 44 | resolve: null, 45 | }; 46 | 47 | decide(decision: boolean) { 48 | this.exitWarning.show = false; 49 | this.exitWarning.resolve(decision); 50 | } 51 | 52 | canDeactivate() { 53 | this.exitWarning.show = true; 54 | return new Promise((resolve) => { 55 | this.exitWarning.resolve = resolve; 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.css: -------------------------------------------------------------------------------- 1 | input.ng-valid { 2 | border-left-style: solid; 3 | border-left-color: forestgreen; 4 | border-left-width: 5px; 5 | } 6 | 7 | input.ng-invalid { 8 | border-left-style: solid; 9 | border-left-color: hotpink; 10 | border-left-width: 5px; 11 | } 12 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.html: -------------------------------------------------------------------------------- 1 |

Flight Search (Reactive) !

2 | 3 | 6 | 7 |
8 |

Dynamisches Formular

9 | @for (item of formDesc; track item) { 10 |
11 | 12 | 13 |
14 | } 15 | 16 |

Statisches Formular

17 | 18 |
19 | 20 | 21 | 22 | @if (!filter.controls.from.valid && !filter.controls.from.pending) { 23 |
24 | Validierungsfehler. Bitte prüfen Sie Ihre Eingaben. 25 |
26 | } 27 | 28 | @if (filter.controls.from.hasError('city')) { 29 |
30 | Diese Stadt wird nicht angefolgen 31 |
32 | } 33 | 34 | @if (filter.controls.from.hasError('required')) { 35 |
36 | Dieses Feld ist ein Pflichtfeld 37 |
38 | } 39 |
40 | 41 |
42 | 43 | 44 |
45 |
46 | 54 |
55 |
56 | 59 | 71 | 72 |
73 | @for (f of flights; track f) { 74 |
75 | 76 | 80 | 85 | 86 |
87 | } 88 |
89 | 90 | 94 | 95 |
96 |
 97 |     Warenkorb
 98 |     ----------------------
 99 |     {{ selectedFlight | json }}
100 |     
102 |
103 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-search-reactive/flight-search-reactive.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Flight } from '../../entities/flight'; 3 | import { FlightService } from '../services/flight.service'; 4 | import { 5 | AbstractControl, 6 | UntypedFormBuilder, 7 | UntypedFormGroup, 8 | Validators, 9 | } from '@angular/forms'; 10 | 11 | @Component({ 12 | selector: 'flight-search-reactive', 13 | templateUrl: 'flight-search-reactive.component.html', 14 | providers: [FlightService], 15 | styleUrls: ['flight-search-reactive.component.css'], 16 | standalone: false, 17 | }) 18 | export class FlightSearchReactiveComponent { 19 | public flights: Array = []; 20 | public selectedFlight: Flight; 21 | 22 | public filter: UntypedFormGroup; 23 | 24 | public formDesc = []; 25 | 26 | constructor( 27 | private flightService: FlightService, 28 | private fb: UntypedFormBuilder 29 | ) { 30 | this.formDesc.push({ 31 | label: 'Von', 32 | name: 'from', 33 | }); 34 | 35 | this.formDesc.push({ 36 | label: 'Nach', 37 | name: 'to', 38 | }); 39 | 40 | this.filter = fb.group({ 41 | from: [ 42 | 'Graz', 43 | [ 44 | Validators.required, 45 | Validators.minLength(3), 46 | (c: AbstractControl): any => { 47 | if (c.value != 'Graz' && c.value != 'Hamburg') { 48 | return { 49 | city: true, 50 | }; 51 | } 52 | return {}; 53 | }, 54 | ], 55 | ], 56 | to: ['Hamburg'], 57 | }); 58 | 59 | this.filter.valueChanges.subscribe((e) => { 60 | console.debug('formular geändert', e); 61 | }); 62 | 63 | this.filter.controls['from'].valueChanges.subscribe((e) => { 64 | console.debug('from geändert', e); 65 | }); 66 | } 67 | 68 | public select(f: Flight): void { 69 | this.selectedFlight = f; 70 | } 71 | 72 | public search(): void { 73 | var value = this.filter.value; 74 | 75 | this.flightService.find(value.from, value.to); 76 | 77 | // .map(function(resp) { return resp.json() }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-search/flight-search.component.css: -------------------------------------------------------------------------------- 1 | input.ng-valid { 2 | border-left-style: solid; 3 | border-left-color: forestgreen; 4 | border-left-width: 5px; 5 | } 6 | 7 | input.ng-invalid { 8 | border-left-style: solid; 9 | border-left-color: hotpink; 10 | border-left-width: 5px; 11 | } 12 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-search/flight-search.component.html: -------------------------------------------------------------------------------- 1 |

Flight Search!

2 | 3 | 6 | 7 |
8 | @if (f?.control?.hasError('round-trip')) { 9 |
10 | Rund-Flüge sind nicht möglich. 11 |
12 | } 13 | 14 |
15 | 16 | 27 | 28 | @if (!f?.controls?.from?.valid && !f?.controls?.from?.pending) { 29 |
30 | Validierungsfehler. Bitte prüfen Sie Ihre Eingaben. 31 |
 32 |           {{ f.controls.from?.errors | json }}
 33 |           
35 |
36 | } 37 | 38 | @if (f?.controls?.from?.hasError('async-city')) { 39 |
40 | Async-City: Die Stadt wird gerade wegen eines Unwetters nicht angeflogen. 41 |
42 | } 43 | 44 | @if (f?.controls?.from?.pending) { 45 |
46 | 47 | Validierung wird ausgeführt. Bitte etwas warten! 48 | 49 |
50 | } 51 | 52 | @if (f?.controls?.from?.hasError('required')) { 53 |
54 | Dieses Feld ist ein Pflichtfeld. 55 |
56 | } 57 | 58 | @if (f?.controls?.from?.hasError('city')) { 59 |
60 | Diese Stadt wird nicht angeflogen. 61 |
62 | } 63 | 64 | @if (f?.controls?.from?.hasError('minlength')) { 65 |
66 | Bitte erfassen Sie min. 3 Zeichen. 67 |
68 | } 69 | @if (f?.controls?.from?.hasError('pattern')) { 70 |
71 | Bitte nur Buchstaben erfassen. 72 |
73 | } 74 |
75 | 76 |
77 | 78 | 79 |
80 |
81 | 89 |
90 |
91 | 94 | 106 | 107 |
108 | @for (f of flights; track f) { 109 |
110 | 111 | 115 | 122 |
123 | } 124 |
125 | 126 | 130 | 131 |
132 |
133 |       Warenkorb
134 |       ----------------------
135 |       {{ selectedFlight | json }}
136 |       
138 |
139 | 140 | 141 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/flight-search/flight-search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { OAuthService } from 'angular-oauth2-oidc'; 3 | import { Flight } from '../../entities/flight'; 4 | import { FlightService } from '../services/flight.service'; 5 | 6 | @Component({ 7 | selector: 'flight-search', 8 | templateUrl: './flight-search.component.html', 9 | styleUrls: ['./flight-search.component.css'], 10 | standalone: false, 11 | }) 12 | export class FlightSearchComponent { 13 | public from: string = 'Graz'; 14 | public to: string = ''; 15 | public selectedFlight: Flight; 16 | 17 | constructor( 18 | private flightService: FlightService, 19 | private oauthService: OAuthService 20 | ) { 21 | console.debug('access-token', this.oauthService.getAccessToken()); 22 | } 23 | 24 | // cmp.flights 25 | public get flights() { 26 | return this.flightService.flights; 27 | } 28 | 29 | public select(f: Flight): void { 30 | this.selectedFlight = f; 31 | } 32 | 33 | public search(): void { 34 | this.flightService.find(this.from, this.to); 35 | 36 | // .map(function(resp) { return resp.json() }) 37 | } 38 | 39 | refresh() { 40 | this.oauthService.oidc = true; 41 | 42 | if ( 43 | !this.oauthService.useSilentRefresh && 44 | this.oauthService.responseType === 'code' 45 | ) { 46 | this.oauthService 47 | .refreshToken() 48 | .then((info) => console.debug('refresh ok', info)) 49 | .catch((err) => console.error('refresh error', err)); 50 | } else { 51 | this.oauthService 52 | .silentRefresh() 53 | .then((info) => console.debug('silent refresh ok', info)) 54 | .catch((err) => console.error('silent refresh error', err)); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/passenger-search/passenger-search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { OAuthService } from 'angular-oauth2-oidc'; 3 | 4 | @Component({ 5 | template: ` 6 |

PassengerSearch

7 |

Platzhalter-Seite. Hier könnte auch Ihre Werbung stehen ;-)

8 |

9 | `, 10 | standalone: false, 11 | }) 12 | export class PassengerSearchComponent implements OnInit { 13 | constructor(private oauthService: OAuthService) {} 14 | ngOnInit() {} 15 | 16 | refresh() { 17 | this.oauthService.silentRefresh(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-booking/services/flight.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; 3 | import { BASE_URL } from '../../app.tokens'; 4 | import { Flight } from '../../entities/flight'; 5 | import { OAuthService } from 'angular-oauth2-oidc'; 6 | 7 | @Injectable() 8 | export class FlightService { 9 | constructor( 10 | private oauthService: OAuthService, 11 | private http: HttpClient, 12 | @Inject(BASE_URL) private baseUrl: string 13 | ) {} 14 | 15 | public flights: Array = []; 16 | 17 | find(from: string, to: string): void { 18 | let url = this.baseUrl + '/api/flight'; 19 | let headers = new HttpHeaders().set('Accept', 'application/json'); 20 | //.set('Authorization', 'Bearer ' + this.oauthService.getAccessToken()); 21 | 22 | let params = new HttpParams().set('from', from).set('to', to); 23 | 24 | this.http.get(url, { headers, params }).subscribe( 25 | (flights) => { 26 | this.flights = flights; 27 | }, 28 | (err) => { 29 | console.warn('status', err.status); 30 | } 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /projects/sample/src/app/flight-history/flight-history.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |

Flight History

6 |
    7 |
  • Graz - Hamburg
  • 8 |
  • Hamburg - Frankfurt
  • 9 |
  • Frankfurt - Graz
  • 10 |
11 | `, 12 | standalone: false, 13 | }) 14 | export class FlightHistoryComponent {} 15 | -------------------------------------------------------------------------------- /projects/sample/src/app/password-flow-login/password-flow-login.component.html: -------------------------------------------------------------------------------- 1 | @if (!givenName) { 2 |

Welcome!

3 | } 4 | @if (givenName) { 5 |

Welcome, {{ givenName }} {{ familyName }}!

6 | } 7 | 8 |
9 |
10 |

Login with Username/Password

11 | 12 | @if (loginFailed) { 13 |

14 | Login wasn't successfull. 15 |

16 | } 17 | 18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 30 | 31 |
32 |
33 |
34 | 35 |
36 |
Username/Password: max/geheim
37 |
38 | 39 |
40 |
41 |

access_token_expiration: {{ access_token_expiration }}

42 |
43 |
44 | 45 |
46 |
47 |

access_token: {{ access_token }}

48 | @if (userProfile) { 49 |
50 | user profile: 51 |
{{ userProfile | json }}
52 |
53 | } 54 |
55 |
56 | -------------------------------------------------------------------------------- /projects/sample/src/app/password-flow-login/password-flow-login.component.ts: -------------------------------------------------------------------------------- 1 | import { authPasswordFlowConfig } from '../auth-password-flow.config'; 2 | import { OAuthService } from 'angular-oauth2-oidc'; 3 | import { Component, OnInit } from '@angular/core'; 4 | 5 | @Component({ 6 | selector: 'app-password-flow-login', 7 | templateUrl: './password-flow-login.component.html', 8 | standalone: false, 9 | }) 10 | export class PasswordFlowLoginComponent implements OnInit { 11 | userName: string; 12 | password: string; 13 | loginFailed: boolean = false; 14 | userProfile: object; 15 | 16 | constructor(private oauthService: OAuthService) { 17 | // Tweak config for password flow 18 | // This is just needed b/c this demo uses both, 19 | // implicit flow as well as password flow 20 | 21 | this.oauthService.configure(authPasswordFlowConfig); 22 | this.oauthService.loadDiscoveryDocument(); 23 | } 24 | 25 | ngOnInit() {} 26 | 27 | loadUserProfile(): void { 28 | this.oauthService.loadUserProfile().then((up) => (this.userProfile = up)); 29 | } 30 | 31 | get access_token() { 32 | return this.oauthService.getAccessToken(); 33 | } 34 | 35 | get access_token_expiration() { 36 | return this.oauthService.getAccessTokenExpiration(); 37 | } 38 | 39 | get givenName() { 40 | var claims = this.oauthService.getIdentityClaims(); 41 | if (!claims) return null; 42 | return claims['given_name']; 43 | } 44 | 45 | get familyName() { 46 | var claims = this.oauthService.getIdentityClaims(); 47 | if (!claims) return null; 48 | return claims['family_name']; 49 | } 50 | 51 | loginWithPassword() { 52 | this.oauthService 53 | .fetchTokenUsingPasswordFlowAndLoadUserProfile( 54 | this.userName, 55 | this.password 56 | ) 57 | .then(() => { 58 | console.debug('successfully logged in'); 59 | this.loginFailed = false; 60 | }) 61 | .catch((err) => { 62 | console.error('error logging in', err); 63 | this.loginFailed = true; 64 | }); 65 | } 66 | 67 | logout() { 68 | this.oauthService.logOut(true); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { OAuthService } from 'angular-oauth2-oidc'; 4 | 5 | @Injectable() 6 | export class AuthGuard { 7 | constructor(private router: Router, private oauthService: OAuthService) {} 8 | 9 | canActivate() { 10 | if ( 11 | this.oauthService.hasValidAccessToken() && 12 | this.oauthService.hasValidIdToken() 13 | ) { 14 | return true; 15 | } else { 16 | this.router.navigate(['/home', { login: true }]); 17 | return false; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/date/custom-date-time-provider.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { DateTimeProvider } from 'angular-oauth2-oidc'; 3 | 4 | // Enabled this provider will make the sample to fail, since the demo IdP is correctly synced to world time. 5 | // This is just a sample of the implementation, if you need it. 6 | @Injectable() 7 | export class CustomDateTimeProvider extends DateTimeProvider { 8 | now(): number { 9 | return Date.now() - 10000000; 10 | } 11 | 12 | new(): Date { 13 | // Implement your custom date. 14 | return new Date(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/date/date.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'date-component', 5 | template: ` 6 |
{{ day }}.{{ month }}.{{ year }} {{ hour }}:{{ minute }}
7 | `, 8 | standalone: false, 9 | }) 10 | export class DateComponent implements OnInit, OnChanges { 11 | @Input() date: string; 12 | 13 | day; 14 | month; 15 | year; 16 | hour; 17 | minute; 18 | 19 | constructor() { 20 | console.debug('ctrl'); 21 | } 22 | 23 | ngOnInit() {} 24 | 25 | ngOnChanges(change) { 26 | // if(change.date) { ... } 27 | 28 | console.debug('change', change); 29 | 30 | let date = new Date(this.date); 31 | 32 | this.day = date.getDate(); 33 | this.month = date.getMonth() + 1; 34 | this.year = date.getFullYear(); 35 | 36 | this.hour = date.getHours(); 37 | this.minute = date.getMinutes(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/pipes/city.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'city', 5 | pure: true, 6 | standalone: false, 7 | }) 8 | export class CityPipe implements PipeTransform { 9 | transform(value: any, ...args: any[]): any { 10 | let fmt = args[0]; // short, long 11 | let short, long; 12 | 13 | switch (value) { 14 | case 'Graz': 15 | long = 'Flughafen Graz Thalerhof'; 16 | short = 'GRZ'; 17 | break; 18 | case 'Hamburg': 19 | long = 'Airport Hamburg Fuhlsbüttl Helmut Schmidt'; 20 | short = 'HAM'; 21 | break; 22 | default: 23 | long = short = 'ROM'; 24 | } 25 | 26 | if (fmt == 'short') return short; 27 | return long; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/preload/custom-preloading.strategy.ts: -------------------------------------------------------------------------------- 1 | import { PreloadingStrategy, Route } from '@angular/router'; 2 | import { Observable } from 'rxjs'; 3 | 4 | export class CustomPreloadingStrategy implements PreloadingStrategy { 5 | preload(route: Route, fn: () => Observable): Observable { 6 | //return Observable.of(true).delay(7000).flatMap(_ => fn()); 7 | 8 | if (true) { 9 | return fn(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { CommonModule } from '@angular/common'; 4 | import { CityPipe } from './pipes/city.pipe'; 5 | import { CityValidatorDirective } from './validation/city.validator'; 6 | import { RoundTrip } from './validation/roundtrip.validator'; 7 | import { AsyncCityValidatorDirective } from './validation/async-city.validator'; 8 | import { DateComponent } from './date/date.component'; 9 | import { AuthGuard } from './auth/auth.guard'; 10 | import { CustomPreloadingStrategy } from './preload/custom-preloading.strategy'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | FormsModule, // [(ngModel)] 15 | CommonModule, // ngFor, ngIf, ngStyle, ngClass, date, json 16 | ], 17 | providers: [], 18 | declarations: [ 19 | CityPipe, 20 | CityValidatorDirective, 21 | AsyncCityValidatorDirective, 22 | RoundTrip, 23 | DateComponent, 24 | ], 25 | exports: [ 26 | CityPipe, 27 | CityValidatorDirective, 28 | AsyncCityValidatorDirective, 29 | RoundTrip, 30 | DateComponent, 31 | ], 32 | }) 33 | export class SharedModule { 34 | static forRoot(): ModuleWithProviders { 35 | return { 36 | providers: [AuthGuard, CustomPreloadingStrategy], 37 | ngModule: SharedModule, 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/validation/async-city.validator.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | import { AbstractControl, NG_ASYNC_VALIDATORS } from '@angular/forms'; 3 | 4 | @Directive({ 5 | selector: 'input[async-city]', 6 | providers: [ 7 | { 8 | provide: NG_ASYNC_VALIDATORS, 9 | useExisting: AsyncCityValidatorDirective, 10 | multi: true, 11 | }, 12 | ], 13 | standalone: false, 14 | }) 15 | export class AsyncCityValidatorDirective { 16 | validate(ctrl: AbstractControl): Promise { 17 | return new Promise((resolve: Function) => { 18 | setTimeout(() => { 19 | if (ctrl.value == 'Graz' || ctrl.value == 'Hamburg') { 20 | resolve({}); 21 | return; 22 | } 23 | 24 | resolve({ 'async-city': false }); 25 | }, 100); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/validation/city.validator.ts: -------------------------------------------------------------------------------- 1 | import { Attribute, Directive } from '@angular/core'; 2 | import { 3 | AbstractControl, 4 | NG_VALIDATORS, 5 | UntypedFormGroup, 6 | Validator, 7 | } from '@angular/forms'; 8 | 9 | @Directive({ 10 | selector: 'input[city]', // 11 | providers: [ 12 | { 13 | provide: NG_VALIDATORS, 14 | useExisting: CityValidatorDirective, 15 | multi: true, 16 | }, 17 | ], 18 | standalone: false, 19 | }) 20 | export class CityValidatorDirective implements Validator { 21 | // @Input() city: string; 22 | 23 | constructor(@Attribute('city') private city: string) {} 24 | 25 | validate(c: AbstractControl): any { 26 | let formGroup = c.root; 27 | let otherValueCtrl = formGroup.controls['to']; 28 | 29 | if (!otherValueCtrl) return {}; 30 | 31 | let otherValue = otherValueCtrl.value; 32 | 33 | if (otherValue == c.value) { 34 | return { 35 | city: 'rundflug', 36 | }; 37 | } 38 | 39 | if (!this.city) return {}; 40 | 41 | let allowed = this.city.split(','); //['Graz', 'Hamburg', 'Wien', 'Frankfurt']; 42 | 43 | if (allowed.indexOf(c.value) == -1) { 44 | return { 45 | city: true, 46 | }; 47 | } 48 | 49 | return {}; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /projects/sample/src/app/shared/validation/roundtrip.validator.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | import { 3 | AbstractControl, 4 | NG_VALIDATORS, 5 | UntypedFormGroup, 6 | Validator, 7 | } from '@angular/forms'; 8 | 9 | @Directive({ 10 | selector: 'form[round-trip]', 11 | providers: [{ provide: NG_VALIDATORS, useExisting: RoundTrip, multi: true }], 12 | standalone: false, 13 | }) 14 | export class RoundTrip implements Validator { 15 | validate(control: AbstractControl): any { 16 | let formGroup = control; 17 | let fromCtrl = formGroup.controls['from']; 18 | let toCtrl = formGroup.controls['to']; 19 | 20 | if (!fromCtrl || !toCtrl) return {}; 21 | 22 | let from = fromCtrl.value; 23 | let to = toCtrl.value; 24 | 25 | if (from == to) { 26 | return { 27 | 'round-trip': { 28 | city: from, 29 | }, 30 | }; 31 | } 32 | return {}; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /projects/sample/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/sample/src/assets/.gitkeep -------------------------------------------------------------------------------- /projects/sample/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /projects/sample/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | }; 9 | -------------------------------------------------------------------------------- /projects/sample/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/angular-oauth2-oidc/234901d598a14f98ec4ef3f648a9c1eb17dd9eb3/projects/sample/src/favicon.ico -------------------------------------------------------------------------------- /projects/sample/src/flags.ts: -------------------------------------------------------------------------------- 1 | // Use HashLocationStrategy for routing? 2 | export const useHash = false; 3 | 4 | // Set this to true, to use silent refresh; otherwise the example 5 | // uses the refresh_token via an AJAX coll to get new tokens. 6 | export const useSilentRefreshForCodeFlow = false; 7 | -------------------------------------------------------------------------------- /projects/sample/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sample 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /projects/sample/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { environment } from './environments/environment'; 5 | 6 | import { AppModule } from './app/app.module'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /projects/sample/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/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | 23 | /** Evergreen browsers require these. **/ 24 | 25 | /*************************************************************************************************** 26 | * Zone JS is required by Angular itself. 27 | */ 28 | import 'zone.js'; // Included with Angular CLI. 29 | 30 | /*************************************************************************************************** 31 | * APPLICATION IMPORTS 32 | */ 33 | 34 | /** 35 | * Date, currency, decimal and percent pipes. 36 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 37 | */ 38 | // import 'intl'; // Run `npm install --save intl`. 39 | /** 40 | * Need to import at least one locale-data with intl. 41 | */ 42 | // import 'intl/locale-data/jsonp/en'; 43 | -------------------------------------------------------------------------------- /projects/sample/src/silent-refresh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /projects/sample/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /projects/sample/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 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting(), 14 | { 15 | teardown: { destroyAfterEach: false }, 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /projects/sample/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /projects/sample/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /projects/sample/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts", "src/polyfills.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "esModuleInterop": true, 9 | "moduleResolution": "bundler", 10 | "allowJs": true, 11 | "experimentalDecorators": true, 12 | "module": "esnext", 13 | "target": "ES2022", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2017", 19 | "dom" 20 | ], 21 | "paths": { 22 | "sample": [ 23 | "dist/sample" 24 | ], 25 | "angular-oauth2-oidc": [ 26 | "projects/lib/src/public_api" 27 | ], 28 | "angular-oauth2-oidc-jwks": [ 29 | "projects/angular-oauth2-oidc-jwks/src/public-api" 30 | ] 31 | }, 32 | "useDefineForClassFields": false 33 | }, 34 | "angularCompilerOptions": { 35 | "enableIvy": true, 36 | "compilationMode": "full" 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.npm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "paths": {} 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/codelyzer"], 3 | "rules": { 4 | "arrow-return-shorthand": true, 5 | "callable-types": true, 6 | "class-name": true, 7 | "comment-format": [true, "check-space"], 8 | "curly": false, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "eofline": false, 13 | "forin": true, 14 | "import-blacklist": [true, "rxjs/Rx"], 15 | "import-spacing": true, 16 | "indent": [true, "spaces"], 17 | "interface-over-type-literal": true, 18 | "label-position": true, 19 | "max-line-length": [true, 140], 20 | "member-access": false, 21 | "member-ordering": [ 22 | true, 23 | { 24 | "order": [ 25 | "static-field", 26 | "instance-field", 27 | "static-method", 28 | "instance-method" 29 | ] 30 | } 31 | ], 32 | "no-arg": true, 33 | "no-bitwise": true, 34 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 35 | "no-construct": true, 36 | "no-debugger": true, 37 | "no-duplicate-super": true, 38 | "no-empty": false, 39 | "no-empty-interface": true, 40 | "no-eval": true, 41 | "no-inferrable-types": [true, "ignore-params"], 42 | "no-misused-new": true, 43 | "no-non-null-assertion": true, 44 | "no-shadowed-variable": true, 45 | "no-string-literal": false, 46 | "no-string-throw": true, 47 | "no-switch-case-fall-through": true, 48 | "no-trailing-whitespace": true, 49 | "no-unnecessary-initializer": true, 50 | "no-unused-expression": true, 51 | "no-var-keyword": true, 52 | "object-literal-sort-keys": false, 53 | "one-line": [ 54 | true, 55 | "check-open-brace", 56 | "check-catch", 57 | "check-else", 58 | "check-whitespace" 59 | ], 60 | "prefer-const": false, 61 | "quotemark": [true, "single"], 62 | "radix": true, 63 | "semicolon": [true, "always"], 64 | "triple-equals": [true, "allow-null-check"], 65 | "typedef-whitespace": [ 66 | true, 67 | { 68 | "call-signature": "nospace", 69 | "index-signature": "nospace", 70 | "parameter": "nospace", 71 | "property-declaration": "nospace", 72 | "variable-declaration": "nospace" 73 | } 74 | ], 75 | "unified-signatures": true, 76 | "variable-name": false, 77 | "whitespace": [ 78 | true, 79 | "check-branch", 80 | "check-operator", 81 | "check-separator", 82 | "check-type" 83 | ], 84 | "no-output-on-prefix": true, 85 | "no-inputs-metadata-property": true, 86 | "no-outputs-metadata-property": true, 87 | "no-host-metadata-property": true, 88 | "no-input-rename": true, 89 | "no-output-rename": true, 90 | "use-lifecycle-interface": true, 91 | "use-pipe-transform-interface": true, 92 | "component-class-suffix": true, 93 | "directive-class-suffix": true 94 | } 95 | } 96 | --------------------------------------------------------------------------------