Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/calm-waves-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@forgerock/iframe-manager': minor
'@forgerock/storage': minor
'@forgerock/sdk-oidc': minor
'@forgerock/davinci-client': minor
'@forgerock/oidc-client': minor
---

Implemented token exchange within OIDC Client
63 changes: 50 additions & 13 deletions e2e/oidc-app/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,72 @@
import { oidc } from '@forgerock/oidc-client';

async function app() {
const oidcClient = await oidc({
config: {
clientId: 'WebOAuthClient',
redirectUri: 'http://localhost:8443/',
scope: 'openid',
serverConfig: {
wellknown:
'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
},
// const pingAmConfig = {
// config: {
// clientId: 'WebOAuthClient',
// redirectUri: 'http://localhost:8443/',
// scope: 'openid',
// serverConfig: {
// wellknown:
// 'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
// },
// },
// };
const pingOneConfig = {
config: {
clientId: '654b14e2-7cc5-4977-8104-c4113e43c537',
redirectUri: 'http://localhost:8443/',
scope: 'openid',
serverConfig: {
wellknown:
'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/.well-known/openid-configuration',
},
});
},
};

async function app() {
const oidcClient = await oidc(pingOneConfig);

// create object from URL query parameters
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
// const state = urlParams.get('state');
const state = urlParams.get('state');
// get error and error_description if they exist
const error = urlParams.get('error');
// const errorDescription = urlParams.get('error_description');

// Handle background authorization flow
if (!code && !error) {
const response = await oidcClient.authorize.background();

if ('error' in response) {
console.error('Authorization Error:', response);
// window.location.assign(response.redirectUrl);

if (response.redirectUrl) {
window.location.assign(response.redirectUrl);
} else {
console.log('Authorization failed with no ability to redirect:', response);
}
return;

// Handle success response from background authorization
} else if ('code' in response) {
console.log('Authorization Code:', response.code);
const tokenResponse = await oidcClient.token.exchange(response.code, response.state);
if ('error' in response) {
console.error('Token Exchange Error:', tokenResponse);
} else {
console.log('Token Exchange Response:', tokenResponse);
}
}

// Handle the user redirecting after authentication
} else if (code && state) {
const response = await oidcClient.token.exchange(code, state);

if ('error' in response) {
console.error('Token Exchange Error:', response);
} else {
console.log('Token Exchange Response:', response);
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/davinci-client/src/lib/client.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
}) {
const log = loggerFn({ level: logger?.level || 'error', custom: logger?.custom });
const store = createClientStore({ requestMiddleware, logger: log });
const serverInfo = createStorage<ContinueNode['server']>(
{ storeType: 'localStorage' },
'serverInfo',
);
const serverInfo = createStorage<ContinueNode['server']>({
type: 'localStorage',
name: 'serverInfo',
});
if (!config.serverConfig.wellknown) {
const error = new Error(
'`wellknown` property is a required as part of the `config.serverConfig`',
Expand Down
2 changes: 1 addition & 1 deletion packages/oidc-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A generic OpenID Connect (OIDC) client library for JavaScript and TypeScript, de

```js
// Initialize OIDC Client
const oidcClient = oidc({
const oidcClient1 = oidc({
/* config */
});

Expand Down
1 change: 1 addition & 0 deletions packages/oidc-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@forgerock/sdk-oidc": "workspace:*",
"@forgerock/sdk-request-middleware": "workspace:*",
"@forgerock/sdk-types": "workspace:*",
"@forgerock/storage": "workspace:*",
"@reduxjs/toolkit": "catalog:",
"effect": "^3.12.7"
},
Expand Down
41 changes: 24 additions & 17 deletions packages/oidc-client/src/lib/authorize.request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,37 @@ export async function authorizeµ(
* set iframe's to DENY.
*/
return authorizeFetchµ(url).pipe(
Micro.flatMap((response) => {
if ('authorizeResponse' in response) {
log.debug('Received authorize response', response.authorizeResponse);
return Micro.succeed(response.authorizeResponse);
}
log.error('Error in authorize response', response);
return Micro.fail(createAuthorizeErrorµ(response, wellknown, config, options));
}),
Micro.flatMap(
(response): Micro.Micro<AuthorizeSuccessResponse, AuthorizeErrorResponse, never> => {
if ('authorizeResponse' in response) {
log.debug('Received authorize response', response.authorizeResponse);
return Micro.succeed(response.authorizeResponse);
}
log.error('Error in authorize response', response);
// For redirection, we need to remore `pi.flow` from the options
const redirectOptions = options;
delete redirectOptions.responseMode;
return createAuthorizeErrorµ(response, wellknown, config, options);
},
),
);
} else {
/**
* If the response mode is not pi.flow, then we are likely using a traditional
* redirect based server supporting iframes. An example would be PingAM.
*/
return authorizeIframeµ(url, config).pipe(
Micro.flatMap((response) => {
if ('code' in response && 'state' in response) {
log.debug('Received authorization code', response);
return Micro.succeed(response as unknown as AuthorizeSuccessResponse);
}
log.error('Error in authorize response', response);
const errorResponse = response as unknown as AuthorizeErrorResponse;
return Micro.fail(createAuthorizeErrorµ(errorResponse, wellknown, config, options));
}),
Micro.flatMap(
(response): Micro.Micro<AuthorizeSuccessResponse, AuthorizeErrorResponse, never> => {
if ('code' in response && 'state' in response) {
log.debug('Received authorization code', response);
return Micro.succeed(response as unknown as AuthorizeSuccessResponse);
}
log.error('Error in authorize response', response);
const errorResponse = response as unknown as AuthorizeErrorResponse;
return createAuthorizeErrorµ(errorResponse, wellknown, config, options);
},
),
);
}
}),
Expand Down
4 changes: 2 additions & 2 deletions packages/oidc-client/src/lib/authorize.request.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export interface AuthorizeSuccessResponse {
export interface AuthorizeErrorResponse {
error: string;
error_description: string;
redirectUrl: string; // URL to redirect the user to for re-authorization
type: 'auth_error';
redirectUrl?: string; // URL to redirect the user to for re-authorization
type: 'auth_error' | 'argument_error' | 'wellknown_error';
}
37 changes: 20 additions & 17 deletions packages/oidc-client/src/lib/authorize.request.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ export function authorizeFetchµ(url: string) {

export function authorizeIframeµ(url: string, config: OidcConfig) {
return Micro.tryPromise({
try: () =>
iFrameManager().getParamsByRedirect({
try: () => {
const params = iFrameManager().getParamsByRedirect({
url,
/***
* https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
Expand All @@ -55,15 +55,17 @@ export function authorizeIframeµ(url: string, config: OidcConfig) {
successParams: ['code', 'state'],
errorParams: ['error', 'error_description'],
timeout: config.serverConfig.timeout || 3000,
}),
});
return params;
},
catch: (err) => {
let message = 'Error fetching authorization URL';
let message = 'Error calling authorization URL';
if (err instanceof Error) {
message = err.message;
}

return {
error: 'Authorization Notwork Failure',
error: 'Authorization Network Failure',
error_description: message,
type: 'auth_error',
} as AuthorizeErrorResponse;
Expand Down Expand Up @@ -102,18 +104,10 @@ export function createAuthorizeErrorµ(
options: GetAuthorizationUrlOptions,
) {
return Micro.tryPromise({
try: async () => {
const url = await createAuthorizeUrl(wellknown.authorization_endpoint, {
try: () =>
createAuthorizeUrl(wellknown.authorization_endpoint, {
...options,
prompt: 'none',
});
return {
error: 'AuthorizationUrlError',
error_description: `Error creating authorization URL for ${url}`,
type: 'auth_error',
redirectUrl: url,
} as AuthorizeErrorResponse;
},
}),
catch: (error) => {
let message = 'Error creating authorization URL';
if (error instanceof Error) {
Expand All @@ -125,7 +119,16 @@ export function createAuthorizeErrorµ(
type: 'auth_error',
} as AuthorizeErrorResponse;
},
});
}).pipe(
Micro.flatMap((url) => {
return Micro.fail({
error: res.error,
error_description: res.error_description,
type: 'auth_error',
redirectUrl: url,
} as AuthorizeErrorResponse);
}),
);
}

export function createAuthorizeUrlµ(
Expand Down
23 changes: 23 additions & 0 deletions packages/oidc-client/src/lib/authorize.slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';

const authorizeSlice = createApi({
reducerPath: 'authorizeSlice',
baseQuery: fetchBaseQuery({
credentials: 'include',
prepareHeaders: (headers) => {
headers.set('Content-Type', 'application/json');
headers.set('Accept', 'application/json');
headers.set('x-requested-with', 'ping-sdk');
headers.set('x-requested-platform', 'javascript');

return headers;
},
}),
endpoints: (builder) => ({
handleAuthorize: builder.query<string, string>({
query: (authorizeUrl) => authorizeUrl,
}),
}),
});

export { authorizeSlice };
Loading
Loading