Skip to content

Commit 375d0ca

Browse files
committed
feat(oidc-client): improve Effect usage
1 parent 42b48fb commit 375d0ca

11 files changed

Lines changed: 257 additions & 150 deletions

File tree

.changeset/dirty-queens-design.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44
'@forgerock/oidc-client': minor
55
---
66

7-
authorize functionality in oidc-client
7+
Implement authorize functionality in oidc-client
8+
9+
- Provide authorize URL method for URL creation
10+
- Provide background method for authorization without redirection
11+
- Introduce Micro from the Effect package

e2e/oidc-app/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async function app() {
2626

2727
if ('error' in response) {
2828
console.error('Authorization Error:', response);
29-
window.location.assign(response.redirectUrl);
29+
// window.location.assign(response.redirectUrl);
3030
return;
3131
} else if ('code' in response) {
3232
console.log('Authorization Code:', response.code);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ export const davinciApi = createApi({
267267
login: 'redirect', // TODO: improve this in SDK to be more semantic
268268
redirectUri: state?.config?.redirectUri,
269269
responseType: state?.config?.responseType as 'code',
270+
responseMode: 'pi.flow',
270271
scope: state?.config?.scope,
271272
});
272273
const url = new URL(authorizeUrl);

packages/oidc-client/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
17
export * from './lib/client.store.js';
Lines changed: 54 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,70 @@
1-
import { iFrameManager } from '@forgerock/iframe-manager';
2-
import { createAuthorizeUrl, GetAuthorizationUrlOptions } from '@forgerock/sdk-oidc';
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import { CustomLogger } from '@forgerock/sdk-logger';
8+
import { GetAuthorizationUrlOptions } from '@forgerock/sdk-oidc';
39
import { Micro } from 'effect';
410

5-
import { createAuthorizeOptions, handleError, handleResponse } from './authorize.request.utils.js';
11+
import {
12+
authorizeFetchµ,
13+
createAuthorizeUrlµ,
14+
authorizeIframeµ,
15+
buildAuthorizeOptionsµ,
16+
createAuthorizeErrorµ,
17+
} from './authorize.request.utils.js';
618

719
import type { WellKnownResponse } from '@forgerock/sdk-types';
820

921
import type { OidcConfig } from './config.types.js';
22+
import { AuthorizeErrorResponse, AuthorizeSuccessResponse } from './authorize.request.types.js';
1023

11-
export async function authorize(
24+
export async function authorizeµ(
1225
wellknown: WellKnownResponse,
1326
config: OidcConfig,
27+
log: CustomLogger,
1428
options?: GetAuthorizationUrlOptions,
1529
) {
16-
const authorizePath = wellknown.authorization_endpoint;
17-
const optionsWithDefaults = createAuthorizeOptions(config, options);
18-
19-
let response: Record<string, unknown>;
20-
21-
try {
22-
/**
23-
* If we support the pi.flow field, this means we are using a PingOne server.
24-
* PingOne servers do not support redirection through iframes because they
25-
* set iframe's to DENY.
26-
*/
27-
if (wellknown.response_modes_supported?.includes('pi.flow')) {
28-
/**
29-
* We need to make a post (or a get) request and both are supported by
30-
* PingOne.
31-
*/
32-
const authorizeUrlMicro = Micro.promise(() =>
33-
createAuthorizeUrl(authorizePath, {
34-
...optionsWithDefaults,
35-
prompt: 'none',
36-
responseMode: 'pi.flow',
37-
}),
38-
);
39-
40-
const fetchMicro = (url: string) =>
41-
Micro.promise(() =>
42-
fetch(url, {
43-
method: 'POST',
44-
credentials: 'include',
30+
return buildAuthorizeOptionsµ(wellknown, config, options).pipe(
31+
Micro.flatMap(([url, config, options]) => createAuthorizeUrlµ(url, config, options)),
32+
Micro.tap((url) => log.debug('Authorize URL created', url)),
33+
Micro.tapError((url) => Micro.sync(() => log.error('Error creating authorize URL', url))),
34+
Micro.flatMap(([url, config, options]) => {
35+
if (options.responseMode === 'pi.flow') {
36+
/**
37+
* If we support the pi.flow field, this means we are using a PingOne server.
38+
* PingOne servers do not support redirection through iframes because they
39+
* set iframe's to DENY.
40+
*/
41+
return authorizeFetchµ(url).pipe(
42+
Micro.flatMap((response) => {
43+
if ('authorizeResponse' in response) {
44+
log.debug('Received authorize response', response.authorizeResponse);
45+
return Micro.succeed(response.authorizeResponse);
46+
}
47+
log.error('Error in authorize response', response);
48+
return Micro.fail(createAuthorizeErrorµ(response, wellknown, config, options));
4549
}),
4650
);
47-
48-
const authorizeRequest = authorizeUrlMicro.pipe(
49-
Micro.flatMap(fetchMicro),
50-
Micro.flatMap((response) => Micro.promise(response.json)),
51-
);
52-
response = await Micro.runPromise(authorizeRequest);
53-
} else {
54-
const authorizeUrlMicro = Micro.promise(() =>
55-
createAuthorizeUrl(authorizePath, {
56-
...optionsWithDefaults,
57-
prompt: 'none',
58-
}),
59-
);
60-
61-
const iframeMicro = (url: string) =>
62-
Micro.promise(() =>
63-
iFrameManager().getParamsByRedirect({
64-
url,
65-
/***
66-
* https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
67-
* The client MUST ignore unrecognized response parameters.
68-
*/
69-
successParams: ['code', 'state'],
70-
errorParams: ['error', 'error_description'],
71-
timeout: config.serverConfig.timeout || 3000,
51+
} else {
52+
/**
53+
* If the response mode is not pi.flow, then we are likely using a traditional
54+
* redirect based server supporting iframes. An example would be PingAM.
55+
*/
56+
return authorizeIframeµ(url, config).pipe(
57+
Micro.flatMap((response) => {
58+
if ('code' in response && 'state' in response) {
59+
log.debug('Received authorization code', response);
60+
return Micro.succeed(response as unknown as AuthorizeSuccessResponse);
61+
}
62+
log.error('Error in authorize response', response);
63+
const errorResponse = response as unknown as AuthorizeErrorResponse;
64+
return Micro.fail(createAuthorizeErrorµ(errorResponse, wellknown, config, options));
7265
}),
7366
);
74-
75-
const authorizeRequest = authorizeUrlMicro.pipe(Micro.flatMap(iframeMicro));
76-
response = await Micro.runPromise(authorizeRequest);
77-
}
78-
79-
// Normalize response, for both success and failure, to handle both
80-
// fetch and iframe
81-
return await handleResponse(response, authorizePath, optionsWithDefaults);
82-
} catch (error) {
83-
// If an error occurs, we return an error response with the authorize URL
84-
// so the application can handle the redirect.
85-
return handleError(error, authorizePath, optionsWithDefaults);
86-
}
67+
}
68+
}),
69+
);
8770
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
17
export interface AuthorizeSuccessResponse {
28
code: string;
39
state: string;

0 commit comments

Comments
 (0)