Open sadeqhatami opened 5 years ago
Any updates on this topic ?
@ramax495 Did you find any solution for this problem ?
@herve-brun Unfortunately, I could not find a solution to this problem,so I used this library
I came upon this issue while trying to integrate Okta Authorization Code + PKCE into ngx-admin... Figured out a way with https://github.com/okta/okta-angular If anybody is interested here is my solution:
import { Inject, Injectable } from '@angular/core';
import { forkJoin, from, Observable, of as observableOf } from 'rxjs';
import { map, switchMap, catchError, mapTo } from 'rxjs/operators';
import {
NbOAuth2AuthStrategy,
NbOAuth2ResponseType,
NbAuthOAuth2JWTToken,
NbOAuth2AuthStrategyOptions,
NbAuthStrategyClass,
NbAuthResult,
auth2StrategyOptions,
} from '@nebular/auth';
import { HttpClient } from '@angular/common/http';
import { NB_WINDOW } from '@nebular/theme';
import { ActivatedRoute } from '@angular/router';
import { OktaAuthService, UserClaims } from '@okta/okta-angular';
export interface OktaToken {
user: UserClaims;
idToken: string;
accessToken: string;
}
export class OktaToken extends NbAuthOAuth2JWTToken {
// let's rename it to exclude name clashes
static NAME = 'nb:auth:okta:token';
protected readonly token: OktaToken;
getValue(): string {
return this.token.accessToken;
}
}
@Injectable()
export class OktaAuthStrategy extends NbOAuth2AuthStrategy {
static setup(options: NbOAuth2AuthStrategyOptions): [NbAuthStrategyClass, NbOAuth2AuthStrategyOptions] {
return [OktaAuthStrategy, options];
}
protected redirectResultHandlers: { [key: string]: Function } = {
[NbOAuth2ResponseType.CODE]: () => {
return from(this.oktaAuth.handleAuthentication())
.pipe(
switchMap(() => {
return forkJoin({
user: this.oktaAuth.getUser(),
idToken: this.oktaAuth.getIdToken(),
accessToken: this.oktaAuth.getAccessToken(),
}) as Observable<OktaToken>;
}),
map((res) => {
return new NbAuthResult(
true,
{},
this.getOption('redirect.success'),
[],
this.getOption('defaultMessages'),
this.createToken(res, this.getOption(`${module}.requireValidToken`)));
}),
catchError((e) => {
console.error('Caught authentication error', e);
return observableOf(
new NbAuthResult(
false,
this.route.snapshot.queryParams,
this.getOption('redirect.failure'),
this.getOption('defaultErrors'),
[],
));
}),
);
},
};
protected defaultOptions: NbOAuth2AuthStrategyOptions = auth2StrategyOptions;
constructor(protected http: HttpClient,
protected route: ActivatedRoute,
protected oktaAuth: OktaAuthService,
@Inject(NB_WINDOW) protected window: any) {
super(http, route, window);
}
authenticate(data?: any): Observable<NbAuthResult> {
return this.isRedirectResult()
.pipe(
switchMap((result: boolean) => {
if (!result) {
this.oktaAuth.login();
return observableOf(new NbAuthResult(true));
}
return this.getAuthorizationResult();
}),
);
}
logout(): Observable<NbAuthResult> {
return from(this.oktaAuth.logout()).pipe(
mapTo(new NbAuthResult(true)),
);
}
}
In core.module.ts
export const NB_CORE_PROVIDERS = [
// Other providers..
OktaAuthModule,
{
provide: OKTA_CONFIG, useValue: config,
},
...NbAuthModule.forRoot({
strategies: [
OktaAuthStrategy.setup({ // Uses Okta's Auth service under the hood
name: 'okta',
clientId: '',
authorize: {
responseType: NbOAuth2ResponseType.CODE,
},
token: {
class: OktaToken,
},
}),
],
forms: {
login: {
strategy: 'okta',
},
logout: {
strategy: 'okta',
},
},
}).providers,
OktaAuthStrategy,
// Other providers...
]
@herve-brun Unfortunately, I could not find a solution to this problem,so I used this library
Do you mind sharing your code for this implementation?
@ColinM9991 here is my implementation of Auth0 with angular-auth-oidc-client
v11.2.1. Hope this helps
import { Inject, Injectable } from '@angular/core';
import { Observable, of, of as observableOf, throwError } from 'rxjs';
import { map, switchMap, catchError } from 'rxjs/operators';
import {
NbOAuth2AuthStrategy,
NbOAuth2ResponseType,
NbAuthOAuth2JWTToken,
NbOAuth2AuthStrategyOptions,
NbAuthStrategyClass,
NbAuthResult,
auth2StrategyOptions,
} from '@nebular/auth';
import { HttpClient, HttpParams } from '@angular/common/http';
import { NB_WINDOW } from '@nebular/theme';
import { ActivatedRoute } from '@angular/router';
import { OidcSecurityService, PublicConfiguration } from 'angular-auth-oidc-client';
export interface Auth0Claims {
email: string;
email_verified: boolean;
family_name?: string;
given_name?: string;
locale?: string;
name: string;
nickname: string;
picture: string;
sub: string;
updated_at: string;
}
export interface Auth0Token {
user: Auth0Claims;
idToken: string;
accessToken: string;
}
export class Auth0JWTToken extends NbAuthOAuth2JWTToken {
// let's rename it to exclude name clashes
static NAME = 'nb:auth:auth0:token';
protected readonly token: Auth0Token;
getValue(): string {
return this.token.accessToken;
}
}
@Injectable()
export class Auth0AuthStrategy extends NbOAuth2AuthStrategy {
static setup(options: NbOAuth2AuthStrategyOptions): [NbAuthStrategyClass, NbOAuth2AuthStrategyOptions] {
return [Auth0AuthStrategy, options];
}
protected redirectResultHandlers: { [key: string]: Function } = {
[NbOAuth2ResponseType.CODE]: () => {
return this.oidcService.checkAuth()
.pipe(
switchMap((isAuthenticated) => {
if (isAuthenticated) {
return this.oidcService.userData$;
}
return throwError('Authentication error');
}),
map((user) => ({
user,
idToken: this.oidcService.getIdToken(),
accessToken: this.oidcService.getToken(),
} as Auth0Token)),
map((res) => {
return new NbAuthResult(
true,
{},
this.getOption('redirect.success'),
[],
this.getOption('defaultMessages'),
this.createToken(res, this.getOption(`${module}.requireValidToken`)));
}),
catchError((e) => {
console.error('Caught authentication error', e);
return observableOf(
new NbAuthResult(
false,
this.route.snapshot.queryParams,
this.getOption('redirect.failure'),
this.getOption('defaultErrors'),
[],
));
}),
);
},
};
protected defaultOptions: NbOAuth2AuthStrategyOptions = auth2StrategyOptions;
constructor(protected http: HttpClient,
protected route: ActivatedRoute,
protected oidcService: OidcSecurityService,
@Inject(NB_WINDOW) protected window: any) {
super(http, route, window);
}
authenticate(data?: any): Observable<NbAuthResult> {
return this.isRedirectResult()
.pipe(
switchMap((result: boolean) => {
if (!result) {
this.oidcService.authorize({
customParams: {
// Audience is required if we want to receive JWT tokens.
// If not sent, we receive opaque access tokens.
// See https://auth0.com/docs/api/authentication#authorization-code-flow-with-pkce
audience: '<audience here>',
// prompt: 'login',
},
});
return observableOf(new NbAuthResult(true));
}
return this.getAuthorizationResult();
}),
);
}
logout(): Observable<NbAuthResult> {
// 1st logout from OidcSecurityService level
// 2nd logout from Auth0
// 3rd, on callback, logout from NbAuthService
return this.isLogoutRedirect().pipe(
switchMap((isRedirect) => {
if (!isRedirect) {
this.performLogout();
}
return of(new NbAuthResult(true));
}),
);
}
private isLogoutRedirect(): Observable<boolean> {
return of(this.window.location.href === this.oidcService.configuration.configuration.postLogoutRedirectUri);
}
private performLogout(): void {
// Auth0 does not expose end_session_endpoint in the discovery document (sadly they're not spec compliant), so we must do it manually.
// First we will logoff locally, then we will navigate to auth0 to finish the logout
this.oidcService.logoff();
// Leave this check for future proofing if ever auth0 exposes end_session
if (!this.oidcService.configuration.wellknown.endSessionEndpoint) {
// No end session was set, craft our own logout url
this.window.location.href = Auth0AuthStrategy.buildLogoutUrl(this.oidcService.configuration);
}
}
/**
* @see https://auth0.com/docs/api/authentication#logout
*/
private static buildLogoutUrl(config: PublicConfiguration): string {
let logoutUrl = Auth0AuthStrategy.ensureTrailingSlash(config.configuration.stsServer) + 'v2/logout';
let params = new HttpParams();
params = params.set('client_id', config.configuration.clientId);
if (config.configuration.postLogoutRedirectUri) {
params = params.append('returnTo', config.configuration.postLogoutRedirectUri);
}
logoutUrl += `?${params}`;
return logoutUrl;
}
private static ensureTrailingSlash(str: string): string {
return str.endsWith('/') ? str : str + '/';
}
}
Thank you @dizco.
Looks like I was along the right lines, I just need to factor in OIDC and login redirects now which shouldn't be too bad.
@dizco I followed your instructions, invalid accessToken and IDToken as shown below: my code: https://github.com/xmlking/yeti/blob/develop/libs/core/src/lib/services/okta-auth.strategy.ts https://github.com/xmlking/yeti/blob/develop/libs/core/src/lib/core.module.ts wonder if someone can help where I am doing wrong...
@xmlking can you log the OktaToken before you feed it to the map()? The error you're getting comes from https://github.com/akveo/nebular/blob/656ed0fa463aa35c62926de5ec5f17ffeea1494e/src/framework/auth/services/token/token.ts#L78
This tells me that this.oktaAuth.getAccessToken()
probably doesn't return what you expect
Yes , I tried. this.oktaAuth.getAccessToken() is printed as ‘A’ and IDToken as ‘g’ , only single character, which is random every time I SSO. The this.oktaAuth.getUser() call works fine
hi i want implement NbOAuth2AuthStrategy with Code Flow + PKCE
my code is
but this code dont generate
i want do it like this linke how i can do it ?