Skip to content

Commit beb349a

Browse files
committed
feat(oidc-client): use Effect within token exchange
1 parent 3129ce2 commit beb349a

19 files changed

Lines changed: 314 additions & 290 deletions

.changeset/calm-waves-change.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@forgerock/iframe-manager': minor
3+
'@forgerock/storage': minor
4+
'@forgerock/sdk-oidc': minor
5+
'@forgerock/davinci-client': minor
6+
'@forgerock/oidc-client': minor
7+
---
8+
9+
Implemented token exchange within OIDC Client

e2e/oidc-app/src/main.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import { oidc } from '@forgerock/oidc-client';
22

3-
async function app() {
4-
const oidcClient = await oidc({
5-
config: {
6-
clientId: 'WebOAuthClient',
7-
redirectUri: 'http://localhost:8443/',
8-
scope: 'openid',
9-
serverConfig: {
10-
wellknown:
11-
'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
12-
},
3+
// const pingAmConfig = {
4+
// config: {
5+
// clientId: 'WebOAuthClient',
6+
// redirectUri: 'http://localhost:8443/',
7+
// scope: 'openid',
8+
// serverConfig: {
9+
// wellknown:
10+
// 'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
11+
// },
12+
// },
13+
// };
14+
const pingOneConfig = {
15+
config: {
16+
clientId: '654b14e2-7cc5-4977-8104-c4113e43c537',
17+
redirectUri: 'http://localhost:8443/',
18+
scope: 'openid',
19+
serverConfig: {
20+
wellknown:
21+
'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/.well-known/openid-configuration',
1322
},
14-
});
23+
},
24+
};
25+
26+
async function app() {
27+
const oidcClient = await oidc(pingOneConfig);
1528

1629
// create object from URL query parameters
1730
const urlParams = new URLSearchParams(window.location.search);
@@ -21,16 +34,32 @@ async function app() {
2134
const error = urlParams.get('error');
2235
// const errorDescription = urlParams.get('error_description');
2336

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

2741
if ('error' in response) {
2842
console.error('Authorization Error:', response);
29-
window.location.assign(response.redirectUrl);
43+
44+
if (response.redirectUrl) {
45+
window.location.assign(response.redirectUrl);
46+
} else {
47+
console.log('Authorization failed with no ability to redirect:', response);
48+
}
3049
return;
50+
51+
// Handle success response from background authorization
3152
} else if ('code' in response) {
3253
console.log('Authorization Code:', response.code);
54+
const tokenResponse = await oidcClient.token.exchange(response.code, response.state);
55+
if ('error' in response) {
56+
console.error('Token Exchange Error:', tokenResponse);
57+
} else {
58+
console.log('Token Exchange Response:', tokenResponse);
59+
}
3360
}
61+
62+
// Handle the user redirecting after authentication
3463
} else if (code && state) {
3564
const response = await oidcClient.token.exchange(code, state);
3665

packages/davinci-client/src/lib/client.store.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
6666
}) {
6767
const log = loggerFn({ level: logger?.level || 'error', custom: logger?.custom });
6868
const store = createClientStore({ requestMiddleware, logger: log });
69-
const serverInfo = createStorage<ContinueNode['server']>(
70-
{ storeType: 'localStorage' },
71-
'serverInfo',
72-
);
69+
const serverInfo = createStorage<ContinueNode['server']>({
70+
type: 'localStorage',
71+
name: 'serverInfo',
72+
});
7373
if (!config.serverConfig.wellknown) {
7474
const error = new Error(
7575
'`wellknown` property is a required as part of the `config.serverConfig`',

packages/oidc-client/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A generic OpenID Connect (OIDC) client library for JavaScript and TypeScript, de
44

55
```js
66
// Initialize OIDC Client
7-
const oidcClient = oidc({
7+
const oidcClient1 = oidc({
88
/* config */
99
});
1010

Lines changed: 16 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
<<<<<<< HEAD
21
/*
32
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
43
*
54
* This software may be modified and distributed under the terms
65
* of the MIT license. See the LICENSE file for details.
76
*/
8-
=======
9-
>>>>>>> f35d74ad (feat(oidc-client): improve Effect usage)
107
import { CustomLogger } from '@forgerock/sdk-logger';
118
import { GetAuthorizationUrlOptions } from '@forgerock/sdk-oidc';
129
import { Micro } from 'effect';
@@ -22,39 +19,18 @@ import {
2219
import type { WellKnownResponse } from '@forgerock/sdk-types';
2320

2421
import type { OidcConfig } from './config.types.js';
25-
<<<<<<< HEAD
2622
import { AuthorizeErrorResponse, AuthorizeSuccessResponse } from './authorize.request.types.js';
27-
=======
28-
import { AuthorizeSuccessResponse } from './authorize.request.types.js';
29-
>>>>>>> f35d74ad (feat(oidc-client): improve Effect usage)
3023

3124
export async function authorizeµ(
3225
wellknown: WellKnownResponse,
3326
config: OidcConfig,
3427
log: CustomLogger,
3528
options?: GetAuthorizationUrlOptions,
3629
) {
37-
<<<<<<< HEAD
3830
return buildAuthorizeOptionsµ(wellknown, config, options).pipe(
3931
Micro.flatMap(([url, config, options]) => createAuthorizeUrlµ(url, config, options)),
4032
Micro.tap((url) => log.debug('Authorize URL created', url)),
4133
Micro.tapError((url) => Micro.sync(() => log.error('Error creating authorize URL', url))),
42-
=======
43-
const buildAuthorizeRequestµ = buildAuthorizeOptionsµ(wellknown, config, options).pipe(
44-
Micro.flatMap(([url, config, options]) => createAuthorizeUrlµ(url, config, options)),
45-
(effect) => {
46-
return Micro.matchEffect(effect, {
47-
onSuccess: (url) => {
48-
log.debug('Created authorization URL', { url });
49-
return effect;
50-
},
51-
onFailure: (error) => {
52-
log.error('Error creating authorization URL', { error });
53-
return effect;
54-
},
55-
});
56-
},
57-
>>>>>>> f35d74ad (feat(oidc-client): improve Effect usage)
5834
Micro.flatMap(([url, config, options]) => {
5935
if (options.responseMode === 'pi.flow') {
6036
/**
@@ -63,61 +39,39 @@ export async function authorizeµ(
6339
* set iframe's to DENY.
6440
*/
6541
return authorizeFetchµ(url).pipe(
66-
Micro.flatMap((response) => {
67-
<<<<<<< HEAD
68-
if ('authorizeResponse' in response) {
69-
log.debug('Received authorize response', response.authorizeResponse);
70-
return Micro.succeed(response.authorizeResponse);
71-
}
72-
log.error('Error in authorize response', response);
73-
return Micro.fail(createAuthorizeErrorµ(response, wellknown, config, options));
74-
=======
75-
return Micro.gen(function* () {
42+
Micro.flatMap(
43+
(response): Micro.Micro<AuthorizeSuccessResponse, AuthorizeErrorResponse, never> => {
7644
if ('authorizeResponse' in response) {
7745
log.debug('Received authorize response', response.authorizeResponse);
78-
return yield* Micro.succeed(response.authorizeResponse as AuthorizeSuccessResponse);
46+
return Micro.succeed(response.authorizeResponse);
7947
}
8048
log.error('Error in authorize response', response);
81-
const errorResponse = response as { error: string; error_description: string };
82-
return yield* createAuthorizeErrorµ(errorResponse, wellknown, config, options);
83-
});
84-
>>>>>>> f35d74ad (feat(oidc-client): improve Effect usage)
85-
}),
49+
// For redirection, we need to remore `pi.flow` from the options
50+
const redirectOptions = options;
51+
delete redirectOptions.responseMode;
52+
return createAuthorizeErrorµ(response, wellknown, config, options);
53+
},
54+
),
8655
);
8756
} else {
8857
/**
8958
* If the response mode is not pi.flow, then we are likely using a traditional
9059
* redirect based server supporting iframes. An example would be PingAM.
9160
*/
9261
return authorizeIframeµ(url, config).pipe(
93-
Micro.flatMap((response) => {
94-
<<<<<<< HEAD
95-
if ('code' in response && 'state' in response) {
96-
log.debug('Received authorization code', response);
97-
return Micro.succeed(response as unknown as AuthorizeSuccessResponse);
98-
}
99-
log.error('Error in authorize response', response);
100-
const errorResponse = response as unknown as AuthorizeErrorResponse;
101-
return Micro.fail(createAuthorizeErrorµ(errorResponse, wellknown, config, options));
102-
=======
103-
return Micro.gen(function* () {
62+
Micro.flatMap(
63+
(response): Micro.Micro<AuthorizeSuccessResponse, AuthorizeErrorResponse, never> => {
10464
if ('code' in response && 'state' in response) {
10565
log.debug('Received authorization code', response);
106-
return yield* Micro.succeed(response as unknown as AuthorizeSuccessResponse);
66+
return Micro.succeed(response as unknown as AuthorizeSuccessResponse);
10767
}
10868
log.error('Error in authorize response', response);
109-
const errorResponse = response as { error: string; error_description: string };
110-
return yield* createAuthorizeErrorµ(errorResponse, wellknown, config, options);
111-
});
112-
>>>>>>> f35d74ad (feat(oidc-client): improve Effect usage)
113-
}),
69+
const errorResponse = response as unknown as AuthorizeErrorResponse;
70+
return createAuthorizeErrorµ(errorResponse, wellknown, config, options);
71+
},
72+
),
11473
);
11574
}
11675
}),
11776
);
118-
<<<<<<< HEAD
119-
=======
120-
121-
return Micro.runPromiseExit(buildAuthorizeRequestµ);
122-
>>>>>>> f35d74ad (feat(oidc-client): improve Effect usage)
12377
}

packages/oidc-client/src/lib/authorize.request.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ export interface AuthorizeErrorResponse {
1414
error: string;
1515
error_description: string;
1616
redirectUrl?: string; // URL to redirect the user to for re-authorization
17-
type: 'auth_error' | 'wellknown_error' | 'network_error' | 'unknown_error';
17+
type: 'auth_error' | 'argument_error' | 'wellknown_error';
1818
}

packages/oidc-client/src/lib/authorize.request.utils.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export function authorizeFetchµ(url: string) {
4545

4646
export function authorizeIframeµ(url: string, config: OidcConfig) {
4747
return Micro.tryPromise({
48-
try: () =>
49-
iFrameManager().getParamsByRedirect({
48+
try: () => {
49+
const params = iFrameManager().getParamsByRedirect({
5050
url,
5151
/***
5252
* https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
@@ -55,15 +55,17 @@ export function authorizeIframeµ(url: string, config: OidcConfig) {
5555
successParams: ['code', 'state'],
5656
errorParams: ['error', 'error_description'],
5757
timeout: config.serverConfig.timeout || 3000,
58-
}),
58+
});
59+
return params;
60+
},
5961
catch: (err) => {
60-
let message = 'Error fetching authorization URL';
62+
let message = 'Error calling authorization URL';
6163
if (err instanceof Error) {
6264
message = err.message;
6365
}
6466

6567
return {
66-
error: 'Authorization Notwork Failure',
68+
error: 'Authorization Network Failure',
6769
error_description: message,
6870
type: 'auth_error',
6971
} as AuthorizeErrorResponse;
@@ -102,17 +104,10 @@ export function createAuthorizeErrorµ(
102104
options: GetAuthorizationUrlOptions,
103105
) {
104106
return Micro.tryPromise({
105-
try: async () => {
106-
const url = await createAuthorizeUrl(wellknown.authorization_endpoint, {
107+
try: () =>
108+
createAuthorizeUrl(wellknown.authorization_endpoint, {
107109
...options,
108-
});
109-
return {
110-
error: res.error,
111-
error_description: res.error_description,
112-
type: 'auth_error',
113-
redirectUrl: url,
114-
} as AuthorizeErrorResponse;
115-
},
110+
}),
116111
catch: (error) => {
117112
let message = 'Error creating authorization URL';
118113
if (error instanceof Error) {
@@ -124,7 +119,16 @@ export function createAuthorizeErrorµ(
124119
type: 'auth_error',
125120
} as AuthorizeErrorResponse;
126121
},
127-
});
122+
}).pipe(
123+
Micro.flatMap((url) => {
124+
return Micro.fail({
125+
error: res.error,
126+
error_description: res.error_description,
127+
type: 'auth_error',
128+
redirectUrl: url,
129+
} as AuthorizeErrorResponse);
130+
}),
131+
);
128132
}
129133

130134
export function createAuthorizeUrlµ(

0 commit comments

Comments
 (0)