diff --git a/.gitignore b/.gitignore index 1bdac792..23585a6a 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ Thumbs.db /.firebase TODO.txt + +/pkce-demo diff --git a/out.txt b/out.txt new file mode 100644 index 00000000..31f2912c --- /dev/null +++ b/out.txt @@ -0,0 +1,7 @@ +{ + "token_type": "Bearer", + "expires_in": 3600, + "access_token": "eyJraWQiOiJCMHcxTjVtU2FUM0RkS3ZXVHd6MnYyRk5XT3JMbGJ1elVJbElJVERHT19vIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULmp0N3huZTJhNFd2Xzh0ZUtWUUZ5cGJvaS0zYjhHbW54d2lzZlczVmNvNmciLCJpc3MiOiJodHRwczovL2Rldi0zMzU0Njcub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiJhcGk6Ly9kZWZhdWx0IiwiaWF0IjoxNTYzNjkxNzgyLCJleHAiOjE1NjM2OTUzODIsImNpZCI6IjBvYXRnYThlYkE0SWtHZzVVMzU2IiwidWlkIjoiMDB1dGdiN2hqQ2szNkVKMlMzNTYiLCJzY3AiOlsib3BlbmlkIiwiZW1haWwiLCJncm91cHMiLCJwcm9maWxlIiwiYWRkcmVzcyIsInBob25lIl0sInN1YiI6InJvYi5mZXJndXNvbkByb2JmZXJndXNvbi5vcmciLCJncm91cHMiOlsiRXZlcnlvbmUiLCJVc2VyIiwiQWRtaW5pc3RyYXRvciJdfQ.gbQJRhYuZJZJ53EWZ1wJDExt5h8tVdpU3FPw2V_oV3uFGyWSI9QlGNDsK7kl2erRbJJL811NFxEn14sG1LYicZsizYZf2ykuLtFSmI-94fz-uAF4XP8NzXWP2Kp8e5SGxjWUSR_q-v84o9YV5xUK6YHMAhRCZHpxttVqV6X1j78HVNTgs7q-QsRFZP1tcnI0joNjn8SIS9BKhQXC7HqLMYdpeHvcSI66GwasjGyKLSiMZHgd47uvmbT7lYfFtU0OEwEdIMYmOwQJlYVbJj1iaMcQjPxXHWps2Kjdt2kr9px9ZhLt09L3v_O94qUPgDtoTiLJos9XjyfZ0RZofpOOoQ", + "scope": "openid email groups profile address phone", + "id_token": "eyJraWQiOiJCMHcxTjVtU2FUM0RkS3ZXVHd6MnYyRk5XT3JMbGJ1elVJbElJVERHT19vIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHV0Z2I3aGpDazM2RUoyUzM1NiIsIm5hbWUiOiJSb2IgRmVyZ3Vzb24iLCJlbWFpbCI6InJvYi5mZXJndXNvbkByb2JmZXJndXNvbi5vcmciLCJ2ZXIiOjEsImlzcyI6Imh0dHBzOi8vZGV2LTMzNTQ2Ny5va3RhLmNvbS9vYXV0aDIvZGVmYXVsdCIsImF1ZCI6IjBvYXRnYThlYkE0SWtHZzVVMzU2IiwiaWF0IjoxNTYzNjkxNzgzLCJleHAiOjE1NjM2OTUzODMsImp0aSI6IklELlJieGtGSHJxcTZtX1FNRTBfaUs0dTZPTUZIN181TldsOTJMZWE2S0M2NzAiLCJhbXIiOlsicHdkIl0sImlkcCI6IjAwb3RnYjdla1ZqZnEzSGtLMzU2IiwicHJlZmVycmVkX3VzZXJuYW1lIjoicm9iLmZlcmd1c29uQHJvYmZlcmd1c29uLm9yZyIsImF1dGhfdGltZSI6MTU2MzY5MTc3OCwiYXRfaGFzaCI6IlltX3RIT1Q0TzlLcjVkbnJqWTFkYlEifQ.cW-b2dlJ6uVLc4h52uO-cVUnAJ2kODRn4Oh1gbTxWKDYWiX3LDa4EDdrqXQ4WjzVZalUhPYtbVmZN47zgPSqvgzLKCc-TIogpfFtL-d-RATdFTnT-M4z9UCr98xNEAFeEmGSlISDju63ucPIkBHlvb0MHwDmQyOS_Jn4BWXRulOLR5UHvY6BlRqekQXValfHUW2X1HC_BNLsJ-UMaxf4ErKDXrOHwE3vVbWK95xCLhOJRtqIqUd0zxUYsEmLJ9GWJm6vWaLBPvj6crg6HeUOOz0mLumS-iYoUS4JDyLXdFw8m44qCmfi69sLSIAOYD59X7HHlQRopwxlcLuHkV0gqg" +} diff --git a/projects/auth-okta/src/lib/auth-okta.module.ts b/projects/auth-okta/src/lib/auth-okta.module.ts index 20073473..f3354cec 100644 --- a/projects/auth-okta/src/lib/auth-okta.module.ts +++ b/projects/auth-okta/src/lib/auth-okta.module.ts @@ -8,8 +8,6 @@ import { AuthOktaConfig } from './models/models'; import { AuthOktaConfigService } from './services/config.service'; import { LoginComponent } from './components/login/login.component'; -import { CallbackComponent } from './components/callback/callback.component'; - // // Auth lib // @@ -20,7 +18,6 @@ import { AuthModule } from 'auth'; // Utils lib // -// import { UtilsModule, LoggerService, ConsoleLoggerService } from 'utils'; import { UtilsModule, LoggerService, loggerProviders } from 'utils'; // @@ -38,9 +35,8 @@ import { LibRoutingModule } from './lib-routing.module'; LibRoutingModule // https://angular.io/guide/router#routing-module-order ], - declarations: [ CallbackComponent, LoginComponent ], + declarations: [ LoginComponent ], providers: [ - // { provide: LoggerService, useClass: ConsoleLoggerService } loggerProviders ], exports: [] @@ -48,6 +44,7 @@ import { LibRoutingModule } from './lib-routing.module'; export class AuthOktaModule { constructor(private logger: LoggerService) { + this.logger.info('Auth Okta Module initialised'); } @@ -64,12 +61,3 @@ export class AuthOktaModule { } } - -// https://github.com/okta/samples-js-angular/blob/master/okta-hosted-login/util/default-config.ts - -/* - - // OktaAuthModule.initAuth(oidc), - // { provide: OKTA_CONFIG, useValue: oidc } - -*/ diff --git a/projects/auth-okta/src/lib/components/callback/callback.component.spec.ts b/projects/auth-okta/src/lib/components/callback/callback.component.spec.ts deleted file mode 100644 index 1f683305..00000000 --- a/projects/auth-okta/src/lib/components/callback/callback.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LoginComponent } from '../login/login.component'; - -describe('LoginComponent', () => { - let component: LoginComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ LoginComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/auth-okta/src/lib/components/callback/callback.component.ts b/projects/auth-okta/src/lib/components/callback/callback.component.ts deleted file mode 100644 index d3742f3b..00000000 --- a/projects/auth-okta/src/lib/components/callback/callback.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AfterViewInit, Component, OnInit } from '@angular/core'; - -import { AuthOktaService } from '../../services/auth/auth-okta.service'; - -import { LoggerService } from 'utils'; - -@Component({ template: `` }) -export class CallbackComponent implements AfterViewInit, OnInit { - - constructor(private authService: AuthOktaService, - private logger: LoggerService) { - } - - ngOnInit() { - - this.logger.info('CallbackComponent: ngOnInit()'); - } - - public ngAfterViewInit() { - - this.logger.info('CallbackComponent: ngAfterViewInit()'); - - // this.authService.auth.handleAuthentication(); - } - -} - -// https://github.com/okta/okta-oidc-js/blob/master/packages/okta-angular/src/okta/components/callback.component.ts -// https://github.com/okta/okta-oidc-js/blob/master/packages/okta-angular/src/okta/services/okta.service.ts diff --git a/projects/auth-okta/src/lib/components/login/login.component.ts b/projects/auth-okta/src/lib/components/login/login.component.ts index b303a55c..923bebd9 100644 --- a/projects/auth-okta/src/lib/components/login/login.component.ts +++ b/projects/auth-okta/src/lib/components/login/login.component.ts @@ -20,7 +20,8 @@ export class LoginComponent implements AfterViewInit, OnInit { this.logger.info('LoginComponent: ngAfterViewInit()'); - this.authService.auth.loginRedirect(); + // this.authService.auth.loginRedirect(); + this.authService.auth.authorizationCodeRedirect(); } } diff --git a/projects/auth-okta/src/lib/lib-routing.module.ts b/projects/auth-okta/src/lib/lib-routing.module.ts index f137ff5d..fb56d92b 100644 --- a/projects/auth-okta/src/lib/lib-routing.module.ts +++ b/projects/auth-okta/src/lib/lib-routing.module.ts @@ -2,10 +2,10 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; // import { OktaCallbackComponent } from '@okta/okta-angular'; -import { OktaCallbackComponent } from 'okta-angular'; +import { OktaAuthorizationCodeCallbackComponent } from 'okta-angular'; +import { OktaImplicitCallbackComponent } from 'okta-angular'; import { LoginComponent } from './components/login/login.component'; -import { CallbackComponent } from './components/callback/callback.component'; const routes: Routes = [ @@ -16,12 +16,12 @@ const routes: Routes = [ { path: 'authorization-code/callback', - component: CallbackComponent + component: OktaAuthorizationCodeCallbackComponent }, { path: 'implicit/callback', - component: OktaCallbackComponent + component: OktaImplicitCallbackComponent } ]; diff --git a/projects/flowable/src/lib/services/abstract/collection/collection.service.ts b/projects/flowable/src/lib/services/abstract/collection/collection.service.ts index 3532dd9d..184421c9 100644 --- a/projects/flowable/src/lib/services/abstract/collection/collection.service.ts +++ b/projects/flowable/src/lib/services/abstract/collection/collection.service.ts @@ -38,7 +38,7 @@ export abstract class CollectionService { this.httpOptions = { headers: new HttpHeaders({ - 'Content-Type': 'application/json', + 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(token) }), params: null diff --git a/projects/okta-angular/src/lib/components/authorization-code/authorization-code.component.ts b/projects/okta-angular/src/lib/components/authorization-code/authorization-code.component.ts new file mode 100644 index 00000000..7a1b26c4 --- /dev/null +++ b/projects/okta-angular/src/lib/components/authorization-code/authorization-code.component.ts @@ -0,0 +1,36 @@ +import { Component, OnInit } from '@angular/core'; +// import { ActivatedRoute } from '@angular/router'; + +import { OktaAuthService } from '../../services/auth/okta-auth.service'; + +@Component({ template: `` }) +export class OktaAuthorizationCodeCallbackComponent implements OnInit { + + constructor(private authService: OktaAuthService) { + } + + public ngOnInit() { + + this.authService.handleAuthorizationCodeFlow(); + } + +} + +// https://stackoverflow.com/questions/38255939/how-to-handle-hash-fragments-from-oauth-redirect-urls-in-angular2-rc3-routing/ + +// OIDC Authorization Code Flow (OAuth2 Authorization Code Grant) + +/* + + constructor(private route: ActivatedRoute, + private authService: OktaAuthService) { + } + + const code = this.route.snapshot.paramMap.get('code'); + const state = this.route.snapshot.paramMap.get('state'); + + console.log('code: ' + code + ' state: ' + state); + + this.authService.handleAuthorizationCodeFlow(code, state); + +*/ diff --git a/projects/okta-angular/src/lib/components/callback.component.ts b/projects/okta-angular/src/lib/components/implicit-callback/implicit-callback.component.ts similarity index 75% rename from projects/okta-angular/src/lib/components/callback.component.ts rename to projects/okta-angular/src/lib/components/implicit-callback/implicit-callback.component.ts index 6a1729ca..34c2470e 100644 --- a/projects/okta-angular/src/lib/components/callback.component.ts +++ b/projects/okta-angular/src/lib/components/implicit-callback/implicit-callback.component.ts @@ -12,14 +12,14 @@ import { Component } from '@angular/core'; -import { OktaAuthService } from '../services/okta-auth.service'; +import { OktaAuthService } from '../../services/auth/okta-auth.service'; + +@Component({ template: `` }) +export class OktaImplicitCallbackComponent { -@Component({template: `` }) -export class OktaCallbackComponent { constructor(private okta: OktaAuthService) { - /** - * Handles the response from Okta and parses tokens. - */ - okta.handleAuthentication(); + + okta.handleImplicitFlow(); } + } diff --git a/projects/okta-angular/src/lib/components/login-redirect.component.ts b/projects/okta-angular/src/lib/components/login-redirect/login-redirect.component.ts similarity index 91% rename from projects/okta-angular/src/lib/components/login-redirect.component.ts rename to projects/okta-angular/src/lib/components/login-redirect/login-redirect.component.ts index c546af67..d4891049 100644 --- a/projects/okta-angular/src/lib/components/login-redirect.component.ts +++ b/projects/okta-angular/src/lib/components/login-redirect/login-redirect.component.ts @@ -12,11 +12,13 @@ import { Component } from '@angular/core'; -import { OktaAuthService } from '../services/okta-auth.service'; +import { OktaAuthService } from '../../services/auth/okta-auth.service'; @Component({ template: `` }) export class OktaLoginRedirectComponent { + constructor(private okta: OktaAuthService) { okta.loginRedirect(); } + } diff --git a/projects/okta-angular/src/lib/create-okta-service.ts b/projects/okta-angular/src/lib/create-okta-service.ts index 38266ec0..a4b63090 100644 --- a/projects/okta-angular/src/lib/create-okta-service.ts +++ b/projects/okta-angular/src/lib/create-okta-service.ts @@ -10,10 +10,13 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { OktaConfig } from './models/okta.config'; +// import { DOCUMENT } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; -import { OktaAuthService } from './services/okta-auth.service'; -export function createOktaService(config: OktaConfig, router: Router) { - return new OktaAuthService(config, router); +import { OktaAuthService } from './services/auth/okta-auth.service'; +import { OktaConfig } from './models/okta.config'; + +export function createOktaService(document: any, config: OktaConfig, http: HttpClient, router: Router) { + return new OktaAuthService(document, config, http, router); } diff --git a/projects/okta-angular/src/lib/guards/okta-auth.guard.ts b/projects/okta-angular/src/lib/guards/auth/okta-auth.guard.ts similarity index 91% rename from projects/okta-angular/src/lib/guards/okta-auth.guard.ts rename to projects/okta-angular/src/lib/guards/auth/okta-auth.guard.ts index 595229e0..cb0b7e6f 100644 --- a/projects/okta-angular/src/lib/guards/okta-auth.guard.ts +++ b/projects/okta-angular/src/lib/guards/auth/okta-auth.guard.ts @@ -18,8 +18,8 @@ import { Router } from '@angular/router'; -import { OktaAuthService } from '../services/okta-auth.service'; -import { AuthRequiredFunction } from '../models/auth-required-function'; +import { OktaAuthService } from '../../services/auth/okta-auth.service'; +import { AuthRequiredFunction } from '../../models/auth-required-function'; @Injectable() export class OktaAuthGuard implements CanActivate { @@ -28,8 +28,6 @@ export class OktaAuthGuard implements CanActivate { /** * Gateway for protected route. Returns true if there is a valid accessToken, * otherwise it will cache the route and start the login flow. - * @param route - * @param state */ async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (await this.oktaAuth.isAuthenticated()) { diff --git a/projects/okta-angular/src/lib/models/auth-required-function.ts b/projects/okta-angular/src/lib/models/auth-required-function.ts index 26d6301c..f338a018 100644 --- a/projects/okta-angular/src/lib/models/auth-required-function.ts +++ b/projects/okta-angular/src/lib/models/auth-required-function.ts @@ -12,6 +12,6 @@ import { Router } from '@angular/router'; -import { OktaAuthService } from '../services/okta-auth.service'; +import { OktaAuthService } from '../services/auth/okta-auth.service'; export type AuthRequiredFunction = (oktaAuth: OktaAuthService, router: Router) => void; diff --git a/projects/okta-angular/src/lib/models/okta.config.ts b/projects/okta-angular/src/lib/models/okta.config.ts index d9f120d9..4cf4ae9c 100644 --- a/projects/okta-angular/src/lib/models/okta.config.ts +++ b/projects/okta-angular/src/lib/models/okta.config.ts @@ -14,6 +14,25 @@ import { InjectionToken } from '@angular/core'; import { AuthRequiredFunction } from './auth-required-function'; +export interface OktaConfig { + clientId: string; + code_challenge: string; + code_challenge_method: string; + issuer: string; + onAuthRequired?: AuthRequiredFunction; + redirectUri: string; + responseType: string; + scope: string; + state: string; + testing: { + disableHttpsCheck: boolean + }; +} + +export const OKTA_CONFIG = new InjectionToken('okta.config.angular'); + +/* + export interface TestingObject { disableHttpsCheck: boolean; } @@ -28,4 +47,4 @@ export interface OktaConfig { testing?: TestingObject; } -export const OKTA_CONFIG = new InjectionToken('okta.config.angular'); +*/ diff --git a/projects/okta-angular/src/lib/okta-auth.module.ts b/projects/okta-angular/src/lib/okta-auth.module.ts index ea99edda..d198c40b 100644 --- a/projects/okta-angular/src/lib/okta-auth.module.ts +++ b/projects/okta-angular/src/lib/okta-auth.module.ts @@ -10,24 +10,29 @@ * See the License for the specific language governing permissions and limitations under the License. */ +import { DOCUMENT } from '@angular/common'; import { ModuleWithProviders, NgModule } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; -import { OktaCallbackComponent } from './components/callback.component'; -import { OktaLoginRedirectComponent } from './components/login-redirect.component'; +import { OktaAuthorizationCodeCallbackComponent } from './components/authorization-code/authorization-code.component'; +import { OktaImplicitCallbackComponent } from './components/implicit-callback/implicit-callback.component'; +import { OktaLoginRedirectComponent } from './components/login-redirect/login-redirect.component'; -import { OktaAuthService } from './services/okta-auth.service'; -import { OktaAuthGuard } from './guards/okta-auth.guard'; +import { OktaAuthService } from './services/auth/okta-auth.service'; +import { OktaAuthGuard } from './guards/auth/okta-auth.guard'; import { OktaConfig, OKTA_CONFIG } from './models/okta.config'; import { createOktaService } from './create-okta-service'; -import { Router } from '@angular/router'; @NgModule({ declarations: [ - OktaCallbackComponent, + OktaAuthorizationCodeCallbackComponent, + OktaImplicitCallbackComponent, OktaLoginRedirectComponent ], exports: [ - OktaCallbackComponent, + OktaAuthorizationCodeCallbackComponent, + OktaImplicitCallbackComponent, OktaLoginRedirectComponent ], providers: [ @@ -36,13 +41,16 @@ import { Router } from '@angular/router'; provide: OktaAuthService, useFactory: createOktaService, deps: [ + DOCUMENT, OKTA_CONFIG, + HttpClient, Router ] } ] }) export class OktaAuthModule { + // Deprecated. Your app should provide OKTA_CONFIG directly static initAuth(config: OktaConfig): ModuleWithProviders { return { @@ -53,4 +61,5 @@ export class OktaAuthModule { ] }; } + } diff --git a/projects/okta-angular/src/lib/services/auth/okta-auth.service.ts b/projects/okta-angular/src/lib/services/auth/okta-auth.service.ts new file mode 100644 index 00000000..094c404f --- /dev/null +++ b/projects/okta-angular/src/lib/services/auth/okta-auth.service.ts @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2017-Present, Okta, Inc. and/or its affiliates. All rights reserved. + * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") + * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and limitations under the License. + */ + +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Router, NavigationExtras } from '@angular/router'; + +import { Observable, Observer } from 'rxjs'; + +import * as OktaAuth from '@okta/okta-auth-js'; + +import { + assertIssuer, + assertClientId, + assertRedirectUri, + buildConfigObject +} from '@okta/configuration-validation'; + +import { OKTA_CONFIG, OktaConfig } from '../../models/okta.config'; +import { UserClaims } from '../../models/user-claims'; + +// import packageInfo from '../packageInfo'; +// See: https://github.com/okta/okta-oidc-js/blob/master/util/write-package-info.js + +const packageInfo = { + name: 'okta/okta-angular', + version: '1.2.1' +}; + +@Injectable() +export class OktaAuthService { + + private oktaAuth: OktaAuth; + private config: OktaConfig; + private observers: Observer[]; + $authenticationState: Observable; + + constructor(@Inject(DOCUMENT) private document: any, + @Inject(OKTA_CONFIG) private auth: OktaConfig, + private http: HttpClient, + private router: Router) { + + // Assert Configuration + assertIssuer(auth.issuer, auth.testing); + assertClientId(auth.clientId); + assertRedirectUri(auth.redirectUri); + + this.observers = []; + + this.oktaAuth = new OktaAuth(buildConfigObject(auth)); + + this.oktaAuth.userAgent = `${packageInfo.name}/${packageInfo.version} ${this.oktaAuth.userAgent}`; + + /** + * Scrub scopes to ensure 'openid' is included + */ + auth.scope = this.scrubScopes(auth.scope); + + /** + * Cache the auth config. + */ + this.config = auth; + + this.$authenticationState = new Observable((observer: Observer) => {this.observers.push(observer); }); + } + + /** + * Checks if there is an access token and id token + */ + async isAuthenticated(): Promise { + const accessToken = await this.getAccessToken(); + const idToken = await this.getIdToken(); + return !!(accessToken || idToken); + } + + private async emitAuthenticationState(state: boolean) { + this.observers.forEach(observer => observer.next(state)); + } + + /** + * Returns the current accessToken in the tokenManager. + */ + async getAccessToken(): Promise { + try { + const accessToken = await this.oktaAuth.tokenManager.get('accessToken'); + return accessToken.accessToken; + } catch (err) { + // The user no longer has an existing SSO session in the browser. + // (OIDC error `login_required`) + // Ask the user to authenticate again. + return undefined; + } + } + + /** + * Returns the current idToken in the tokenManager. + */ + async getIdToken(): Promise { + try { + const idToken = await this.oktaAuth.tokenManager.get('idToken'); + return idToken.idToken; + } catch (err) { + // The user no longer has an existing SSO session in the browser. + // (OIDC error `login_required`) + // Ask the user to authenticate again. + return undefined; + } + } + + /** + * Returns user claims from the /userinfo endpoint if an + * accessToken is provided or parses the available idToken. + */ + async getUser(): Promise { + const accessToken = await this.oktaAuth.tokenManager.get('accessToken'); + const idToken = await this.oktaAuth.tokenManager.get('idToken'); + if (accessToken && idToken) { + const userinfo = await this.oktaAuth.token.getUserInfo(accessToken); + if (userinfo.sub === idToken.claims.sub) { + // Only return the userinfo response if subjects match to + // mitigate token substitution attacks + return userinfo; + } + } + return idToken ? idToken.claims : undefined; + } + + /** + * Returns the configuration object used. + */ + getOktaConfig(): OktaConfig { + return this.config; + } + + // https://developer.okta.com/docs/reference/api/oidc/#authorize + + async authorizationCodeRedirect() { + + const url = this.auth.issuer + '/v1/authorize' + + '?response_type=' + encodeURIComponent(this.auth.responseType) + + '&client_id=' + encodeURIComponent(this.auth.clientId) + + '&state=' + encodeURIComponent(this.auth.state) + + '&scope=' + encodeURIComponent(this.auth.scope) + + '&redirect_uri=' + encodeURIComponent(this.auth.redirectUri) + + '&code_challenge=' + encodeURIComponent(this.auth.code_challenge) + + '&code_challenge_method=' + encodeURIComponent(this.auth.code_challenge_method); + + this.document.location.href = url; + } + + /** + * Launches the login redirect. + */ + loginRedirect(fromUri?: string, additionalParams?: object) { + + if (fromUri) { + this.setFromUri(fromUri); + } + + this.oktaAuth.token.getWithRedirect({ + responseType: (this.config.responseType || 'id_token token').split(' '), + // Convert scopes to list of strings + scopes: this.config.scope.split(' '), + ...additionalParams + }); + + } + + /** + * Stores the intended path to redirect after successful login. + */ + setFromUri(uri: string, queryParams?: object) { + const json = JSON.stringify({ + uri: uri, + params: queryParams + }); + localStorage.setItem('referrerPath', json); + } + + /** + * Returns the referrer path from localStorage or app root. + */ + getFromUri(): { uri: string, extras: NavigationExtras } { + const referrerPath = localStorage.getItem('referrerPath'); + localStorage.removeItem('referrerPath'); + + const path = JSON.parse(referrerPath) || { uri: '/', params: {} }; + const navigationExtras: NavigationExtras = { + queryParams: path.params + }; + + return { + uri: path.uri, + extras: navigationExtras + }; + } + + // https://developer.okta.com/docs/reference/api/oidc/#token + + async handleAuthorizationCodeFlow(): Promise { + + const params = new URLSearchParams(this.document.location.search.substring(1)); + const code = params.get('code'); + const state = params.get('state'); + + console.log('code: ' + code); + console.log('state: ' + state); + + const endpoint = this.auth.issuer + '/v1/token'; + + const httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }) + }; + + const body = { + grant_type: 'authorization_code', + client_id: this.auth.clientId, + redirect_uri: this.auth.redirectUri, + code: code, + code_verifier: 'M25iVXpKU3puUjFaYWg3T1NDTDQtcW1ROUY5YXlwalNoc0hhakxifmZHag' + }; + + const urlEncoded = Object.keys(body).map(key => key + '=' + body[key]).join('&'); + + const response = await this.http.post(endpoint, urlEncoded, httpOptions).toPromise() + .catch( error => { + console.log('error: ' + JSON.stringify(error)); + }); + + console.log('response: ' + JSON.stringify(response)); + + if (response.id_token) { + this.oktaAuth.tokenManager.add('idToken', response.id_token); + } + + // ERROR Error: Uncaught (in promise): AuthSdkError: Token must be an Object with scopes, expiresAt, and an idToken or + // accessToken properties + + if (response.access_token) { + this.oktaAuth.tokenManager.add('accessToken', response.access_token); + } + + if (await this.isAuthenticated()) { + this.emitAuthenticationState(true); + } + + const fromUri = this.getFromUri(); + this.router.navigate([fromUri.uri], fromUri.extras); + } + + async handleImplicitFlow(): Promise { + + const tokens = await this.oktaAuth.token.parseFromUrl(); + + tokens.forEach(token => { + + if (token.idToken) { + this.oktaAuth.tokenManager.add('idToken', token); + } + + if (token.accessToken) { + this.oktaAuth.tokenManager.add('accessToken', token); + } + + }); + + if (await this.isAuthenticated()) { + this.emitAuthenticationState(true); + } + + /** + * Navigate back to the initial view or root of application. + */ + const fromUri = this.getFromUri(); + this.router.navigate([fromUri.uri], fromUri.extras); + } + + /** + * Clears the user session in Okta and removes + * tokens stored in the tokenManager. + */ + async logout(uri?: string): Promise { + this.oktaAuth.tokenManager.clear(); + await this.oktaAuth.signOut(); + this.emitAuthenticationState(false); + this.router.navigate([uri || '/']); + } + + /** + * Scrub scopes to ensure 'openid' is included + */ + scrubScopes(scopes: string): string { + if (!scopes) { + return 'openid email'; + } + if (scopes.indexOf('openid') === -1) { + return scopes + ' openid'; + } + return scopes; + } +} + +// https://developer.okta.com/code/javascript/okta_auth_sdk/ + +// https://github.com/okta/okta-auth-js#tokenmanageraddkey-token + +/* + + async authorizationCodeRedirect() { + + const endpoint = this.auth.issuer + '/v1/authorize'; + + const httpOptions = { + headers: new HttpHeaders({ + 'Accept': 'application/json', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json' + }) + }; + + const body = { + response_type: this.auth.responseType, + client_id: this.auth.clientId, + state: this.auth.state, + scope: this.auth.scope, + redirect_uri: this.auth.redirectUri, + code_challenge: this.auth.code_challenge, + code_challenge_method: this.auth.code_challenge_method + }; + + const response = await this.http.post(endpoint, body, httpOptions).toPromise() + .catch( error => { + + console.log('error: ' + error); + }); + + console.log('response' + JSON.stringify(response)); + } + + async handleAuthorizationCodeFlow(code: string, state: string): Promise { + + const endpoint = this.auth.issuer + '/v1/token'; + + const httpOptions = { + headers: new HttpHeaders({ + 'Accept': 'application/json', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json' + }) + }; + + const body = { + grant_type: 'authorization_code', + client_id: this.auth.clientId, + redirect_uri: this.auth.redirectUri, + code: code, + code_verifier: 'M25iVXpKU3puUjFaYWg3T1NDTDQtcW1ROUY5YXlwalNoc0hhakxifmZHag' + }; + + const response = await this.http.post(endpoint, body, httpOptions).toPromise() + .catch( error => { + console.log('error: ' + JSON.stringify(error)); + }); + + console.log('response: ' + JSON.stringify(response)); + + if (response.id_token) { + this.oktaAuth.tokenManager.add('idToken', response.id_token); + } + if (response.access_token) { + this.oktaAuth.tokenManager.add('accessToken', response.access_token); + } + + if (await this.isAuthenticated()) { + this.emitAuthenticationState(true); + } + + const fromUri = this.getFromUri(); + this.router.navigate([fromUri.uri], fromUri.extras); + } + +*/ diff --git a/projects/okta-angular/src/lib/services/okta-auth.service.ts b/projects/okta-angular/src/lib/services/okta-auth.service.ts deleted file mode 100644 index aa11b10b..00000000 --- a/projects/okta-angular/src/lib/services/okta-auth.service.ts +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2017-Present, Okta, Inc. and/or its affiliates. All rights reserved. - * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") - * - * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and limitations under the License. - */ - -import { Inject, Injectable } from '@angular/core'; -import { Router, NavigationExtras } from '@angular/router'; -import { - assertIssuer, - assertClientId, - assertRedirectUri, - buildConfigObject -} from '@okta/configuration-validation'; - -import { OKTA_CONFIG, OktaConfig } from '../models/okta.config'; -import { UserClaims } from '../models/user-claims'; - -// import packageInfo from '../packageInfo'; -// See: https://github.com/okta/okta-oidc-js/blob/master/util/write-package-info.js - -const packageInfo = { - name: 'okta/okta-angular', - version: '1.2.1' -}; - -/** - * Import the okta-auth-js library - */ -import * as OktaAuth from '@okta/okta-auth-js'; -import { Observable, Observer } from 'rxjs'; - -@Injectable() -export class OktaAuthService { - private oktaAuth: OktaAuth; - private config: OktaConfig; - private observers: Observer[]; - $authenticationState: Observable; - - constructor(@Inject(OKTA_CONFIG) private auth: OktaConfig, private router: Router) { - // Assert Configuration - assertIssuer(auth.issuer, auth.testing); - assertClientId(auth.clientId); - assertRedirectUri(auth.redirectUri); - - this.observers = []; - - this.oktaAuth = new OktaAuth(buildConfigObject(auth)); - - this.oktaAuth.userAgent = `${packageInfo.name}/${packageInfo.version} ${this.oktaAuth.userAgent}`; - - /** - * Scrub scopes to ensure 'openid' is included - */ - auth.scope = this.scrubScopes(auth.scope); - - /** - * Cache the auth config. - */ - this.config = auth; - - this.$authenticationState = new Observable((observer: Observer) => {this.observers.push(observer)}) - } - - /** - * Checks if there is an access token and id token - */ - async isAuthenticated(): Promise { - const accessToken = await this.getAccessToken(); - const idToken = await this.getIdToken(); - return !!(accessToken || idToken); - } - - private async emitAuthenticationState(state: boolean) { - this.observers.forEach(observer => observer.next(state)); - } - - /** - * Returns the current accessToken in the tokenManager. - */ - async getAccessToken(): Promise { - try { - const accessToken = await this.oktaAuth.tokenManager.get('accessToken'); - return accessToken.accessToken; - } catch (err) { - // The user no longer has an existing SSO session in the browser. - // (OIDC error `login_required`) - // Ask the user to authenticate again. - return undefined; - } - } - - /** - * Returns the current idToken in the tokenManager. - */ - async getIdToken(): Promise { - try { - const idToken = await this.oktaAuth.tokenManager.get('idToken'); - return idToken.idToken; - } catch (err) { - // The user no longer has an existing SSO session in the browser. - // (OIDC error `login_required`) - // Ask the user to authenticate again. - return undefined; - } - } - - /** - * Returns user claims from the /userinfo endpoint if an - * accessToken is provided or parses the available idToken. - */ - async getUser(): Promise { - const accessToken = await this.oktaAuth.tokenManager.get('accessToken'); - const idToken = await this.oktaAuth.tokenManager.get('idToken'); - if (accessToken && idToken) { - const userinfo = await this.oktaAuth.token.getUserInfo(accessToken); - if (userinfo.sub === idToken.claims.sub) { - // Only return the userinfo response if subjects match to - // mitigate token substitution attacks - return userinfo; - } - } - return idToken ? idToken.claims : undefined; - } - - /** - * Returns the configuration object used. - */ - getOktaConfig(): OktaConfig { - return this.config; - } - - /** - * Launches the login redirect. - * @param fromUri - * @param additionalParams - */ - loginRedirect(fromUri?: string, additionalParams?: object) { - if (fromUri) { - this.setFromUri(fromUri); - } - - this.oktaAuth.token.getWithRedirect({ - responseType: (this.config.responseType || 'id_token token').split(' '), - // Convert scopes to list of strings - scopes: this.config.scope.split(' '), - ...additionalParams - }); - } - - /** - * Stores the intended path to redirect after successful login. - * @param uri - * @param queryParams - */ - setFromUri(uri: string, queryParams?: object) { - const json = JSON.stringify({ - uri: uri, - params: queryParams - }); - localStorage.setItem('referrerPath', json); - } - - /** - * Returns the referrer path from localStorage or app root. - */ - getFromUri(): { uri: string, extras: NavigationExtras } { - const referrerPath = localStorage.getItem('referrerPath'); - localStorage.removeItem('referrerPath'); - - const path = JSON.parse(referrerPath) || { uri: '/', params: {} }; - const navigationExtras: NavigationExtras = { - queryParams: path.params - }; - - return { - uri: path.uri, - extras: navigationExtras - } - } - - /** - * Parses the tokens from the callback URL. - */ - async handleAuthentication(): Promise { - const tokens = await this.oktaAuth.token.parseFromUrl(); - tokens.forEach(token => { - if (token.idToken) { - this.oktaAuth.tokenManager.add('idToken', token); - } - if (token.accessToken) { - this.oktaAuth.tokenManager.add('accessToken', token); - } - }); - if (await this.isAuthenticated()) { - this.emitAuthenticationState(true); - } - /** - * Navigate back to the initial view or root of application. - */ - const fromUri = this.getFromUri(); - this.router.navigate([fromUri.uri], fromUri.extras); - } - - /** - * Clears the user session in Okta and removes - * tokens stored in the tokenManager. - * @param uri - */ - async logout(uri?: string): Promise { - this.oktaAuth.tokenManager.clear(); - await this.oktaAuth.signOut(); - this.emitAuthenticationState(false); - this.router.navigate([uri || '/']); - } - - /** - * Scrub scopes to ensure 'openid' is included - * @param scopes - */ - scrubScopes(scopes: string): string { - if (!scopes) { - return 'openid email'; - } - if (scopes.indexOf('openid') === -1) { - return scopes + ' openid'; - } - return scopes; - } -} diff --git a/projects/okta-angular/src/public_api.ts b/projects/okta-angular/src/public_api.ts index a44fe74c..ec700d81 100644 --- a/projects/okta-angular/src/public_api.ts +++ b/projects/okta-angular/src/public_api.ts @@ -33,11 +33,12 @@ export { UserClaims } from './lib/models/user-claims'; */ -export * from './lib/components/callback.component'; -export * from './lib/components/login-redirect.component'; +export * from './lib/components/authorization-code/authorization-code.component'; +export * from './lib/components/implicit-callback/implicit-callback.component'; +export * from './lib/components/login-redirect/login-redirect.component'; -export * from './lib/services/okta-auth.service'; -export * from './lib/guards/okta-auth.guard'; +export * from './lib/services/auth/okta-auth.service'; +export * from './lib/guards/auth/okta-auth.guard'; export * from './lib/models/okta.config'; export * from './lib/models/user-claims'; diff --git a/projects/utils/src/lib/models/config.ts b/projects/utils/src/lib/models/config.ts index 19a72316..4eda044c 100644 --- a/projects/utils/src/lib/models/config.ts +++ b/projects/utils/src/lib/models/config.ts @@ -17,13 +17,13 @@ export interface Config { oidc: { clientId: string, - code_challenge: string; - code_challenge_method: string; + code_challenge: string, + code_challenge_method: string, issuer: string, redirectUri: string, responseType: string, scope: string, - state: string; + state: string, testing: { disableHttpsCheck: boolean } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 34e5f73c..841fde4f 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -29,7 +29,7 @@ export const environment: Environment = { redirectUri: 'http://localhost:4200/authorization-code/callback', responseType: 'code', scope: 'openid profile email phone address groups', - state: '', + state: 'state-8600b31f-52d1-4dca-987c-386e3d8967e9', testing: { disableHttpsCheck: true } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 9af3bce6..6e424ed1 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -29,7 +29,7 @@ export const environment: Environment = { redirectUri: 'http://localhost:4200/authorization-code/callback', responseType: 'code', scope: 'openid profile email phone address groups', - state: '', + state: 'state-8600b31f-52d1-4dca-987c-386e3d8967e9', testing: { disableHttpsCheck: true }