├── DemoApp ├── src │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── silent-refresh.html │ ├── tsconfig.app.json │ ├── index.html │ ├── tslint.json │ ├── browserslist │ ├── main.ts │ ├── styles.css │ ├── app │ │ ├── app.module.ts │ │ └── app.component.ts │ └── polyfills.ts ├── tsconfig.json ├── README.md ├── package.json ├── tslint.json └── angular.json ├── second-load.png ├── auth0-sign-up.png ├── implicit-flow.png ├── initial-load.png ├── auth0-authorize.png ├── auth0-create-api.png ├── scaffolded-app.png ├── auth0-create-application.png ├── .editorconfig ├── .gitignore └── README.md /DemoApp/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DemoApp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /second-load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/second-load.png -------------------------------------------------------------------------------- /auth0-sign-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/auth0-sign-up.png -------------------------------------------------------------------------------- /implicit-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/implicit-flow.png -------------------------------------------------------------------------------- /initial-load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/initial-load.png -------------------------------------------------------------------------------- /auth0-authorize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/auth0-authorize.png -------------------------------------------------------------------------------- /auth0-create-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/auth0-create-api.png -------------------------------------------------------------------------------- /scaffolded-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/scaffolded-app.png -------------------------------------------------------------------------------- /DemoApp/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/DemoApp/src/favicon.ico -------------------------------------------------------------------------------- /auth0-create-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc/HEAD/auth0-create-application.png -------------------------------------------------------------------------------- /DemoApp/src/silent-refresh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DemoApp/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | max_line_length = off 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /DemoApp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DemoApp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DemoApp/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DemoApp/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /DemoApp/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | **/dist 3 | **/tmp 4 | **/out-tsc 5 | 6 | # dependencies 7 | **/node_modules 8 | 9 | # VSCode 10 | .vscode/* 11 | !.vscode/settings.json 12 | !.vscode/tasks.json 13 | !.vscode/launch.json 14 | !.vscode/extensions.json 15 | 16 | # misc 17 | **/.sass-cache 18 | **/connect.lock 19 | **/coverage 20 | **/libpeerconnection.log 21 | npm-debug.log 22 | yarn-error.log 23 | testem.log 24 | **/typings 25 | 26 | # System Files 27 | .DS_Store 28 | Thumbs.db 29 | -------------------------------------------------------------------------------- /DemoApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2017", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DemoApp/src/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 5 | font-size: 16px; 6 | } 7 | 8 | body { 9 | padding: 20px; 10 | } 11 | 12 | button { 13 | margin: 2px; 14 | padding: 4px 8px; 15 | background: #fed; 16 | border: 1px solid #963; 17 | cursor: pointer; 18 | font-size: 0.85em; 19 | } 20 | 21 | button:hover { 22 | background: #fca; 23 | } 24 | 25 | pre { 26 | border: 1px solid #aaa; 27 | background: #eee; 28 | padding: 10px; 29 | } 30 | -------------------------------------------------------------------------------- /DemoApp/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 | * In development mode, for easier debugging, you can ignore zone related error 11 | * stack frames such as `zone.run`/`zoneDelegate.invokeTask` by importing the 12 | * below file. Don't forget to comment it out in production mode 13 | * because it will have a performance impact when errors are thrown 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /DemoApp/README.md: -------------------------------------------------------------------------------- 1 | # DemoApp 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.1.3. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Further help 18 | 19 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 20 | -------------------------------------------------------------------------------- /DemoApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "lint": "ng lint" 9 | }, 10 | "private": true, 11 | "dependencies": { 12 | "@angular/animations": "^6.1.0", 13 | "@angular/common": "^6.1.0", 14 | "@angular/compiler": "^6.1.0", 15 | "@angular/core": "^6.1.0", 16 | "@angular/forms": "^6.1.0", 17 | "@angular/http": "^6.1.0", 18 | "@angular/platform-browser": "^6.1.0", 19 | "@angular/platform-browser-dynamic": "^6.1.0", 20 | "@angular/router": "^6.1.0", 21 | "angular-oauth2-oidc": "^4.0.3", 22 | "core-js": "^2.5.4", 23 | "rxjs": "^6.0.0", 24 | "zone.js": "~0.8.26" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "~0.7.0", 28 | "@angular/cli": "~6.1.3", 29 | "@angular/compiler-cli": "^6.1.0", 30 | "@angular/language-service": "^6.1.0", 31 | "@types/node": "~8.9.4", 32 | "codelyzer": "~4.2.1", 33 | "ts-node": "~5.0.1", 34 | "tslint": "~5.9.1", 35 | "typescript": "~2.7.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DemoApp/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 | 6 | import { HttpClientModule } from '@angular/common/http'; 7 | import { OAuthModule, AuthConfig, JwksValidationHandler, ValidationHandler, OAuthStorage, OAuthModuleConfig } from 'angular-oauth2-oidc'; 8 | 9 | const config: AuthConfig = { 10 | issuer: 'https://jeroenheijmans.eu.auth0.com/', 11 | clientId: 'GICewG40jdYWEnmuKNFux3MW4auQypSF', 12 | customQueryParams: { audience: 'https://auth0-demo-001.infi.nl' }, 13 | redirectUri: window.location.origin + '/index.html', 14 | silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', 15 | scope: 'openid profile email', 16 | }; 17 | 18 | config.logoutUrl = `${config.issuer}v2/logout?client_id=${config.clientId}&returnTo=${encodeURIComponent(config.redirectUri)}`; 19 | 20 | const authModuleConfig: OAuthModuleConfig = { 21 | // Inject "Authorization: Bearer ..." header for these APIs: 22 | resourceServer: { 23 | allowedUrls: ['http://localhost:8080'], 24 | sendAccessToken: true, 25 | }, 26 | }; 27 | 28 | @NgModule({ 29 | declarations: [ 30 | AppComponent 31 | ], 32 | imports: [ 33 | BrowserModule, 34 | HttpClientModule, 35 | OAuthModule.forRoot(authModuleConfig), 36 | ], 37 | providers: [ 38 | { provide: OAuthModuleConfig, useValue: authModuleConfig }, 39 | { provide: ValidationHandler, useClass: JwksValidationHandler }, 40 | { provide: OAuthStorage, useValue: localStorage }, 41 | { provide: AuthConfig, useValue: config }, 42 | ], 43 | bootstrap: [AppComponent] 44 | }) 45 | export class AppModule { } 46 | -------------------------------------------------------------------------------- /DemoApp/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { OAuthService, OAuthErrorEvent } from 'angular-oauth2-oidc'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | template: `

DemoApp

7 |

You are logged in as {{username}}.

8 |

9 | 10 | 11 | 12 |

13 |

Access Token

{{token | json}}
14 |

Claims

{{claims | json}}
15 | `, 16 | styles: [] 17 | }) 18 | export class AppComponent { 19 | username = ''; 20 | 21 | get token() { return this.oauthService.getAccessToken(); } 22 | get claims() { return this.oauthService.getIdentityClaims(); } 23 | 24 | constructor(private oauthService: OAuthService) { 25 | // For debugging: 26 | oauthService.events.subscribe(e => e instanceof OAuthErrorEvent ? console.error(e) : console.warn(e)); 27 | 28 | // Load information from Auth0 (could also be configured manually) 29 | oauthService.loadDiscoveryDocument() 30 | 31 | // See if the hash fragment contains tokens (when user got redirected back) 32 | .then(() => oauthService.tryLogin()) 33 | 34 | // If we're still not logged in yet, try with a silent refresh: 35 | .then(() => { 36 | if (!oauthService.hasValidAccessToken()) { 37 | return oauthService.silentRefresh(); 38 | } 39 | }) 40 | 41 | // Get username, if possible. 42 | .then(() => { 43 | if (oauthService.getIdentityClaims()) { 44 | this.username = oauthService.getIdentityClaims()['name']; 45 | } 46 | }); 47 | 48 | oauthService.setupAutomaticSilentRefresh(); 49 | } 50 | 51 | login() { this.oauthService.initImplicitFlow(); } 52 | logout() { this.oauthService.logOut(); } 53 | refresh() { this.oauthService.silentRefresh(); } 54 | } 55 | -------------------------------------------------------------------------------- /DemoApp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /DemoApp/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 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Web Animations `@angular/platform-browser/animations` 51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 53 | **/ 54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 55 | 56 | /** 57 | * By default, zone.js will patch all possible macroTask and DomEvents 58 | * user can disable parts of macroTask/DomEvents patch by setting following flags 59 | */ 60 | 61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 64 | 65 | /* 66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 68 | */ 69 | // (window as any).__Zone_enable_cross_context_check = true; 70 | 71 | /*************************************************************************************************** 72 | * Zone JS is required by default for Angular itself. 73 | */ 74 | import 'zone.js/dist/zone'; // Included with Angular CLI. 75 | 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /DemoApp/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "DemoApp": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "inlineTemplate": true, 14 | "inlineStyle": true, 15 | "spec": false 16 | }, 17 | "@schematics/angular:class": { 18 | "spec": false 19 | }, 20 | "@schematics/angular:directive": { 21 | "spec": false 22 | }, 23 | "@schematics/angular:guard": { 24 | "spec": false 25 | }, 26 | "@schematics/angular:module": { 27 | "spec": false 28 | }, 29 | "@schematics/angular:pipe": { 30 | "spec": false 31 | }, 32 | "@schematics/angular:service": { 33 | "spec": false 34 | } 35 | }, 36 | "architect": { 37 | "build": { 38 | "builder": "@angular-devkit/build-angular:browser", 39 | "options": { 40 | "outputPath": "dist/DemoApp", 41 | "index": "src/index.html", 42 | "main": "src/main.ts", 43 | "polyfills": "src/polyfills.ts", 44 | "tsConfig": "src/tsconfig.app.json", 45 | "assets": [ 46 | "src/silent-refresh.html", 47 | "src/favicon.ico", 48 | "src/assets" 49 | ], 50 | "styles": [ 51 | "src/styles.css" 52 | ], 53 | "scripts": [] 54 | }, 55 | "configurations": { 56 | "production": { 57 | "fileReplacements": [ 58 | { 59 | "replace": "src/environments/environment.ts", 60 | "with": "src/environments/environment.prod.ts" 61 | } 62 | ], 63 | "optimization": true, 64 | "outputHashing": "all", 65 | "sourceMap": false, 66 | "extractCss": true, 67 | "namedChunks": false, 68 | "aot": true, 69 | "extractLicenses": true, 70 | "vendorChunk": false, 71 | "buildOptimizer": true 72 | } 73 | } 74 | }, 75 | "serve": { 76 | "builder": "@angular-devkit/build-angular:dev-server", 77 | "options": { 78 | "browserTarget": "DemoApp:build" 79 | }, 80 | "configurations": { 81 | "production": { 82 | "browserTarget": "DemoApp:build:production" 83 | } 84 | } 85 | }, 86 | "extract-i18n": { 87 | "builder": "@angular-devkit/build-angular:extract-i18n", 88 | "options": { 89 | "browserTarget": "DemoApp:build" 90 | } 91 | }, 92 | "lint": { 93 | "builder": "@angular-devkit/build-angular:tslint", 94 | "options": { 95 | "tsConfig": [ 96 | "src/tsconfig.app.json", 97 | "src/tsconfig.spec.json" 98 | ], 99 | "exclude": [ 100 | "**/node_modules/**" 101 | ] 102 | } 103 | } 104 | } 105 | } 106 | }, 107 | "defaultProject": "DemoApp" 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample Auth0 and Angular-OAuth2-OIDC Application 2 | 3 | This repository demonstrates how to connect your Angular 6 application to Auth0 using the implicit flow. 4 | It is the companion to a blog post written for [Infi](https://infi.nl). 5 | 6 | ## ⚠️ Notice about updates 7 | 8 | Note that this repository is provided "as-is" and will most likely not receive any (security) updates. 9 | 10 | ## Disclaimers 11 | 12 | Let's get some disclaimers out of the way. 13 | This repository is **frozen in time**: it was created in **September 2018** and probably never updated. 14 | So you might have to adjust the advice here for your own timeline. 15 | 16 | Second, this repository demonstrates **how to connect the dots** but it also **glosses over details**. 17 | It should help you get started, or grasp the idea. 18 | But please adjust accordingly for production applications. 19 | 20 | ## Let's get started 21 | 22 | This is a very specific, technical, pragmatic post. 23 | It's just what we enjoy at Infi: getting those important details just right. 24 | If you came here for fluffy content about agile, or projects, or fun stuff: better skip this post. 25 | You've been warned! 26 | 27 | Actually, this is one post in a series: 28 | 29 | 1. The "About" part, describing all moving parts and processes. 30 | 1. The "Gimme-teh-codez" part, that walks you through the code. 31 | 32 | If you already know how the Implicit Flow works, you can safely skip parts of the post. 33 | If code says more than words to you, or if you know how Auth0 works, you can safely skip the entire post, and go straight to part 2. 34 | For the rest of us, we'll start at the beginning. 35 | 36 | ## About the things involved 37 | 38 | We'll start by getting our terminology straight. 39 | What's what!? 40 | 41 | ### The Implicit Flow 42 | 43 | OAuth2 and OpenID Connect are standards for how to authenticate and (to some degree) authorize users in your systems. 44 | It assumes this type of setup with three items: 45 | 46 | 1. An **Identity Server** application handles user accounts, passwords, 2FA, and all that good jazz. 47 | 1. **Clients** (like an Angular application) that send their users to the Id Server to log in, (after which they're redirected back to the Client). 48 | 1. Your **API**, which needs the access token on each call to verify access. 49 | 50 | This differs from the slightly simpler (but less secure) **Resource Owner Password** flow. 51 | With the Implicit Flow the Client never sees credentials: users trust only the Id Server with those. 52 | On the downside, you do have some redirection going on for the user. 53 | The user sees login screens from the Id Server, but this should not be a big problem because: 54 | 55 | - Either it's a well-known provider, and users are right to trust it. 56 | - Or it's your own identity server, and you can style things to make it "part of the client experience". 57 | 58 | Oh, and this flow also quite naturally supports external Identity Providers (the "log in with Google/GitHub/etc" stuff). 59 | Which is very nice for users. 60 | 61 | Footnote: read more about [the Implicit Flow in RFC 6749](https://tools.ietf.org/html/rfc6749#section-1.3.2). 62 | 63 | ### The Identity Server 64 | 65 | You can of course create your own Identity Server. 66 | Security is hard though, so don't completely roll your own. 67 | Instead, use an existing solution to build from. 68 | 69 | There's good ones available for nearly every tech stack. 70 | For .NET there's [IdentityServer](https://identityserver.io/), Java e.g. has [spring-oauth-server](https://github.com/authlete/spring-oauth-server), and so forth. 71 | 72 | However, there are also SAAS solutions (sometimes called IDaaS) available. 73 | For example [Okta](https://www.okta.com/), [Keycloak](https://www.keycloak.org/), and [Auth0](https://auth0.com/). 74 | In this tutorial we use **Auth0** (a comparison is left for another time). 75 | 76 | ### The API 77 | 78 | In this post we won't touch on the API side of things. 79 | The beauty of OAuth2 is that the API side of things is largely *decoupled* from the rest. 80 | We will get to the point where access tokens are sent to a dummy API, and assume everything would work from there. 81 | 82 | There is one important note about the flow though. 83 | Tokens are passed plainly to the API by the client application. 84 | The format for such tokens is "JWT" (pronounced like "jot"), typically at least *signed* (JWS), or alternatively *encrypted* (JWE). 85 | Your API can verify (or decrypt) the tokens. 86 | 87 | To do this the API will need to get the public key (or decryption key) from the ID Server. 88 | It typically does so "live", by calling the ID Server (cached and refreshed every so often). 89 | But you can also provide these keys out of band. 90 | 91 | Footnote: read more about [JSON Web Tokens (JWT) in RFC 7519](https://tools.ietf.org/html/rfc7519). 92 | 93 | ### The Client 94 | 95 | For OAuth2, a "Client" is an abstract concept. 96 | It can be a Single Page Web App, a mobile application, a traditional MVC Web App, or even another API. 97 | When talking about "the Client" in this post, we're talking about our Angular 6+ CLI application. 98 | The Implicit Flow is well-suited for Single Page (JavaScript) Applications. 99 | 100 | When a Client determines that a user should log in, it redirects the user to the Identity Server. 101 | The user logs in at the Identity Server, and gets redirected back to the Client. 102 | The Client at this point expects that the user "brings back" the access token (and possibly id token). 103 | This is typically done by the Identity Server redirecting back to the URL of the client with tokens in the hash fragment of the URL. 104 | 105 | We will build an Angular CLI application from scratch in part 2 of this series. 106 | In it we will use one of the available libraries for handling the OAuth2 and OpenID Connect parts: [`angular-oauth2-oidc`](https://github.com/manfredsteyer/angular-oauth2-oidc). 107 | The Angular application will require users to log in with Auth0, and send the retrieved tokens along to the dummy API. 108 | 109 | Footnote: read more about [Clients in RFC 6749](https://tools.ietf.org/html/rfc6749#section-2). 110 | 111 | ## Putting things together 112 | 113 | Let's put what we learned above in a picture: 114 | 115 | ![Implicit Flow diagram](implicit-flow.png) 116 | 117 | The two lines between Client and ID Server in this (simplified!) visualization *are* "the Flow". 118 | They determine how a Client can retrieve a token from the ID Server. 119 | 120 | Some specific things missing in the diagram are: 121 | 122 | - All log in screens being served by the ID Server. We use Auth0 in this series, so it's all taken care of for us. 123 | - Third party logins, e.g. "Log in with Google". This would include a fourth box all the way to the left, but with Auth0 that only requires configuration, no coding on our part. 124 | - Silent refreshes: access tokens are short lived, so you need to get a fresh one every hour or so. Turns out that's just the normal flow in a hidden iframe, how it works is shown when we work on the code. 125 | 126 | And that's all there is to it! 127 | In the next part we will start working on the actual code. 128 | 129 | ## Let's Code 130 | 131 | In the previous part we discussed the OAuth2 Implicit Flow. 132 | But code is a more efficient way of communicating, don't you think? 133 | So let's get to it! 134 | 135 | If you're here just for the code: no problem! 136 | To get a full example of how the client side library is supposed to work, you should check out [the `sample-angular-oauth2-oidc-with-auth-guards` repository](https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards), which has a *production-worthy* example of how to use the library. 137 | It would need to be reconfigured to use Auth0 however. 138 | So if you want to see a *minimal* example of how to use it with Auth0, check out [this post's companion repository](https://github.com/jeroenheijmans/sample-auth0-angular-oauth2-oidc). 139 | 140 | But nothing beats building it yourself. 141 | So here we go. 142 | 143 | ### Prerequisites 144 | 145 | To follow along, you need these things: 146 | 147 | - Node (tested with 10.8.0) and NPM (tested with 6.3.0) 148 | - Angular CLI (tested with 6.1.5) 149 | - An IDE (VS Code is nice for Angular coding) 150 | - A shell (Powershell or Bash will do) 151 | 152 | In addition you will need an [Auth0](https://auth0.com/) account. 153 | You can create one now, or when we get to that part. 154 | 155 | ### Angular setup 156 | 157 | Let's start with this: 158 | 159 | ```bash 160 | ng new DemoApp --inline-style --inline-template --skip-tests --skip-git 161 | cd DemoApp 162 | ng serve --open 163 | ``` 164 | 165 | This should open a default, minimalistic Angular application. 166 | It leaves the console waiting for hot-reload requests. 167 | Next, open the `DemoApp` folder in your editor, and replace `app.component.ts` with this: 168 | 169 | ```typescript 170 | import { Component } from '@angular/core'; 171 | 172 | @Component({ 173 | selector: 'app-root', 174 | template: `

DemoApp

175 |

You are logged in as {{username}}.

176 |

177 | 178 | 179 | 180 |

181 |

Access Token

{{token | json}}
182 |

Claims

{{claims | json}}
183 | `, 184 | styles: [] 185 | }) 186 | export class AppComponent { 187 | username = 'TODO'; 188 | 189 | get token() { return 'TODO'; } 190 | get claims() { return 'TODO'; } 191 | 192 | constructor() { } 193 | 194 | login() { } 195 | logout() { } 196 | refresh() { } 197 | } 198 | ``` 199 | 200 | Your app should look somewhat like this (if you steal some of [these styles](DemoApp/src/styles.css)): 201 | 202 | ![initial app](scaffolded-app.png) 203 | 204 | ### Adding angular-oauth2-oidc 205 | 206 | This section documents the steps to add the client side library. 207 | It's adapted from the library's readme, and the approach used in [the production-usage sample repository](https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards). 208 | Start with this on the command line: 209 | 210 | ```bash 211 | npm install --save angular-oauth2-oidc # Optionally pin to 4.0.3, which we used 212 | ``` 213 | 214 | #### Changing the app.module 215 | 216 | Next, go to `app.module.ts` and make these changes: 217 | 218 | ```typescript 219 | // Existing imports... 220 | import { HttpClientModule } from '@angular/common/http'; // Added 221 | import { OAuthModule, AuthConfig, JwksValidationHandler, ValidationHandler, OAuthStorage, OAuthModuleConfig } from 'angular-oauth2-oidc'; // Added 222 | ``` 223 | 224 | ```typescript 225 | // Could also go to its own file, but we just dump it next to the AppModule. 226 | const config: AuthConfig = { 227 | issuer: 'TODO', 228 | clientId: 'TODO', 229 | redirectUri: window.location.origin + '/index.html', 230 | logoutUrl: 'TODO/v2/logout?returnTo=' + encodeURIComponent(window.location.origin), 231 | silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', 232 | scope: 'openid profile email', 233 | }; 234 | 235 | config.logoutUrl = `${config.issuer}v2/logout?client_id=${config.clientId}&returnTo=${encodeURIComponent(config.redirectUri)}`; 236 | ``` 237 | 238 | ```typescript 239 | // Could also go to its own file, but we just dump it next to the AppModule. 240 | const authModuleConfig: OAuthModuleConfig = { 241 | // Inject "Authorization: Bearer ..." header for these APIs: 242 | resourceServer: { 243 | allowedUrls: ['http://localhost:8080'], 244 | sendAccessToken: true, 245 | }, 246 | }; 247 | ``` 248 | 249 | ```typescript 250 | imports: [ 251 | // Existing imports... 252 | HttpClientModule, // Added 253 | OAuthModule.forRoot(authModuleConfig), // Added 254 | ] 255 | ``` 256 | 257 | ```typescript 258 | providers: [ 259 | { provide: OAuthModuleConfig, useValue: authModuleConfig }, 260 | { provide: ValidationHandler, useClass: JwksValidationHandler }, 261 | { provide: OAuthStorage, useValue: localStorage }, 262 | { provide: AuthConfig, useValue: config }, 263 | ] 264 | ``` 265 | 266 | Some things we'll get back to once we have our Auth0 account set up. 267 | 268 | #### Changing the app.component 269 | 270 | Now let's adjust the `app.component.ts` file: 271 | 272 | ```typescript 273 | import { OAuthService, OAuthErrorEvent } from 'angular-oauth2-oidc'; // Add this import 274 | ``` 275 | 276 | ```typescript 277 | // Leave @Component annotation as is... 278 | // ...just change the class: 279 | export class AppComponent { 280 | username = ''; 281 | 282 | get token() { return this.oauthService.getAccessToken(); } 283 | get claims() { return this.oauthService.getIdentityClaims(); } 284 | 285 | constructor(private oauthService: OAuthService) { 286 | // For debugging: 287 | oauthService.events.subscribe(e => e instanceof OAuthErrorEvent ? console.error(e) : console.warn(e)); 288 | 289 | // Load information from Auth0 (could also be configured manually) 290 | oauthService.loadDiscoveryDocument() 291 | 292 | // See if the hash fragment contains tokens (when user got redirected back) 293 | .then(() => oauthService.tryLogin()) 294 | 295 | // If we're still not logged in yet, try with a silent refresh: 296 | .then(() => { 297 | if (!oauthService.hasValidAccessToken()) { 298 | return oauthService.silentRefresh(); 299 | } 300 | }) 301 | 302 | // Get username, if possible. 303 | .then(() => { 304 | if (oauthService.getIdentityClaims()) { 305 | this.username = oauthService.getIdentityClaims()['name']; 306 | } 307 | }); 308 | 309 | oauthService.setupAutomaticSilentRefresh(); 310 | } 311 | 312 | login() { this.oauthService.initImplicitFlow(); } 313 | logout() { this.oauthService.logOut(); } 314 | refresh() { this.oauthService.silentRefresh(); } 315 | } 316 | ``` 317 | 318 | Let's investigate what's being changed, top to bottom. 319 | First, the `token` and `claims` properties now forward to the OAuthService. 320 | They're just for debugging purposes. 321 | 322 | Next, the `constructor` gets some good 'ole console debugging to assist while coding. 323 | It also has a "promise chain", starting with the loading of the discovery document (you could also manually enter your ID Server's config in the app, to win a bit of loading speed). 324 | This chain continues to do `tryLogin`, which inspects the browser URL to see if the hash fragment contains tokens (after a redirect back by Auth0). 325 | This will also clear the hash fragment. 326 | 327 | In typical production apps, this "chain" will be much longer. 328 | I would suggest adding an attempt to do a "Silent Refresh" to see if you can log in that way. 329 | And there are many other niceties too. 330 | Check out [this example repository](https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards) to see what such a chain would look like. 331 | For now, we'll stick with this simple setup. 332 | 333 | Finally, in the app.component, there are three methods that are just proxies for OAuthService methods. 334 | 335 | #### Silent Refreshes 336 | 337 | To get silent refreshes to work, we need to do one more thing. 338 | Add a file `silent-refresh.html` next to your `index.html` file, with these contents: 339 | 340 | ```html 341 | 342 | 343 | 344 | 345 | 346 | 347 | ``` 348 | 349 | To understand why and how this works, consider how a silent refresh works for the library. 350 | In order, this will happen: 351 | 352 | 1. When a silent refresh is needed (requested, or on a timer), a hidden iframe is created. 353 | 1. That iframe is directed to Auth0, passing along (a) an instruction that no user interaction is possible, and (b) the URL of the `silent-refresh.html` file for redirecting the user back. 354 | 1. The ID Server sees the iframe, uses cookies (if any) for its domain, and checks if the user is logged in. 355 | 1. The ID Server will redirect that iframe back to the silent-refresh URL, using the hash fragment to indicate failure to log in, or otherwise passes along an access token. 356 | 1. The html above loads in the iframe, and communicates to its parent (your app!) what was passed along in the hash fragment. 357 | 358 | To get this file served as part of your Angular app, you need to add it to the `angular.json` file: 359 | 360 | ```javascript 361 | "assets": [ 362 | "src/silent-refresh.html", 363 | // etc. 364 | ``` 365 | 366 | **Important**: you have to restart `ng serve` for this change to be picked up! 367 | 368 | ### Setting up Auth0 369 | 370 | First things first, go [sign up for Auth0](https://auth0.com/signup). 371 | 372 | #### Creating the Client 373 | 374 | Next, go to [manage.auth0.com/#/applications](https://manage.auth0.com/#/applications) to create a Client. 375 | Hit "Create Application", name it "DemoApp", and select "Single Page Web App": 376 | 377 | ![Create Application popup](auth0-create-application.png) 378 | 379 | Skip the "Quick Start", and open the "Settings" tab. 380 | Take care of these base settings: 381 | 382 | - Set "Allowed Callback URLs" to `http://localhost:4200/, http://localhost:4200/index.html, http://localhost:4200/silent-refresh.html` 383 | - Set "Allowed Logout URLs" to `http://localhost:4200/, http://localhost:4200/index.html` 384 | - Note the "Client ID" (not the secret), **you will need it later**. 385 | - Note the "Domain", **you will need it later**. 386 | 387 | Then open up the "Advanced" settings at the bottom. 388 | Go to the "Grant Types" tab and note that the "Implicit" flow is checked (you could uncheck others, if you want) 389 | 390 | #### Creating the Resource (API) 391 | 392 | Go to [manage.auth0.com/#/apis](https://manage.auth0.com/#/apis) to create an API. 393 | Hit "Create API", name it "DemoApi", and give it an *Identifier*. 394 | As explained in the Auth0 UI, this should be a URI to identify your Resource API. 395 | For example, we used `https://auth0-demo-001.infi.nl`: 396 | 397 | ![Create API popup](auth0-create-api.png) 398 | 399 | Note the Identifier (a.k.a. "issuer"), **you will need it later**. 400 | 401 | ### Connecting the dots 402 | 403 | Now that we have both ends set up, we need to connect them. 404 | Let's start by editing the `AuthConfig` we created earlier: 405 | 406 | - Set the `issuer` to your Auth0 domain (with `https://` added), e.g. `https://jeroenheijmans.eu.auth0.com` 407 | - Set the `clientId` to the one mentioned in the dashboard of Auth0 408 | - Set the `audience` in your `customQueryParams` to the identifier you chose earlier 409 | 410 | That's it! 411 | Reload your application (if needed), with devtools open, and you should see this: 412 | 413 | ![Initial Screen](initial-load.png) 414 | 415 | Don't worry about all the red bits! 416 | It's supposed to be like that (for now). 417 | Try hitting the "Log in" button now, and in the subsequent Auth0 screen, choose "Sign Up": 418 | 419 | ![Sign Up](auth0-sign-up.png) 420 | 421 | Then authorize our Angular application: 422 | 423 | ![Authorize](auth0-authorize.png) 424 | 425 | You get redirected back to your Angular application, with tokens in the hash fragment. 426 | Before you can probably spot it, our code will log you in and clear the fragment. 427 | You should see something like this: 428 | 429 | ![Second Screen](second-load.png) 430 | 431 | You're all logged in and good to go! 432 | 433 | ## Conclusions 434 | 435 | Security is hard. 436 | OAuth2 and OpenID Connect make things even more complex. 437 | But we can bring the complexity back down a bit again by using existing components, as well as a SAAS solution like Auth0. 438 | 439 | In these posts you've learned the very basics of setting up Angular and Auth0. 440 | We brought them together, ending with a simple but effective complete package. 441 | 442 | These examples were only the basics. 443 | The client side logic calls for many other more advanced features, including: 444 | 445 | - AuthGuards for protecting routes 446 | - Extended login mechanisms, trying to be as user-friendly as possible 447 | - Handling edge cases and problems 448 | - Enabling session checks (push notifications from ID Servers about Single Sign Out) 449 | - External login providers ("log in with Google", etc.) 450 | - Cross-browser-tab (or window) notifications of events like sign out 451 | - Loading user profiles with additional identity information 452 | - Preserving state (like target URL) before initializing Implicit Flow 453 | 454 | If you want to see how all these things work, you should go to [this example repository](https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards/). 455 | If you clone it and reconfigure it (change `issuer` and `clientId`, add `customQueryParams`), you should be able to "just" connect it to your own Auth0 account, and see how it works. 456 | 457 | Hopefully this post was helpful to you. 458 | It certainly was educational to *write* it. 459 | --------------------------------------------------------------------------------