Skip to content

Commit 26c6983

Browse files
authored
Merge pull request #344 from ForgeRock/justin_oidc-mods
feat(oidc-client): implement authorize API
2 parents 3cb91e7 + 375d0ca commit 26c6983

28 files changed

Lines changed: 732 additions & 126 deletions

.changeset/dirty-queens-design.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@forgerock/iframe-manager': minor
3+
'@forgerock/sdk-oidc': minor
4+
'@forgerock/oidc-client': minor
5+
---
6+
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

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
.npm/_logs
77

88
# Generated code
9+
logs/*
910
tmp/
1011
e2e/**/dist
1112
*/dist/*

CLAUDE.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
This is the Ping JavaScript SDK - a monorepo containing multiple packages for web applications integrating with the Ping platform. The SDK provides APIs for user authentication, device management, and accessing Ping-secured resources.
8+
9+
## Development Commands
10+
11+
### Core Commands
12+
13+
- `pnpm build` - Build all affected packages
14+
- `pnpm test` - Run tests for all affected packages
15+
- `pnpm lint` - Lint all affected packages
16+
- `pnpm format` - Format code using Prettier
17+
- `pnpm nx typecheck` - Run TypeScript type checking
18+
19+
### Package Management
20+
21+
- `pnpm create-package` - Generate a new library package using Nx
22+
- `pnpm nx serve <package-name>` - Serve a specific package in development
23+
- `pnpm nx test <package-name> --watch` - Run tests for a specific package in watch mode
24+
25+
### E2E Testing
26+
27+
- `pnpm test:e2e` - Run end-to-end tests for affected packages
28+
- Individual e2e apps are in `e2e/` directory with their own test suites
29+
30+
## Architecture
31+
32+
### Monorepo Structure
33+
34+
The repository uses **Nx** as the monorepo tool with the following structure:
35+
36+
```
37+
packages/
38+
├── davinci-client/ # DaVinci flow orchestration client
39+
├── device-client/ # Device management (binding, profiles, WebAuthn)
40+
├── oidc-client/ # OpenID Connect authentication client
41+
├── protect/ # Ping Protect fraud detection
42+
├── sdk-effects/ # Effect-based utilities (logger, storage, etc.)
43+
│ ├── iframe-manager/
44+
│ ├── logger/
45+
│ ├── oidc/
46+
│ ├── sdk-request-middleware/
47+
│ └── storage/
48+
├── sdk-types/ # Shared TypeScript types
49+
└── sdk-utilities/ # Common utilities (PKCE, URL handling)
50+
```
51+
52+
### Key Packages
53+
54+
- **davinci-client**: State management for DaVinci authentication flows using Redux Toolkit
55+
- **oidc-client**: OIDC authentication with token management and storage
56+
- **device-client**: Device binding, profiles, OATH, Push, and WebAuthn capabilities
57+
- **protect**: Fraud detection and risk assessment integration
58+
- **sdk-effects**: Effect-based architecture packages for common functionalities
59+
60+
### Technology Stack
61+
62+
- **Package Manager**: pnpm with workspace configuration
63+
- **Build Tool**: Vite for building and bundling
64+
- **Testing**: Vitest for unit tests, Playwright for e2e tests
65+
- **State Management**: Redux Toolkit (in davinci-client)
66+
- **TypeScript**: Strict typing with composite project configuration
67+
- **Linting**: ESLint with Prettier integration
68+
69+
### Nx Configuration
70+
71+
- Uses target defaults for build, test, lint, and typecheck
72+
- Caching enabled for build and test targets
73+
- Workspace layout configured for packages and e2e apps
74+
- Parallel execution limited to 1 for consistency
75+
76+
### Package Dependencies
77+
78+
Packages use workspace references (`workspace:*`) for internal dependencies and catalog references (`catalog:`) for shared external dependencies like `@reduxjs/toolkit`.
79+
80+
### Testing Strategy
81+
82+
- Unit tests: Vitest with coverage reporting
83+
- E2E tests: Playwright with dedicated test apps in `e2e/` directory
84+
- Mock API server available in `e2e/mock-api-v2/` for testing
85+
86+
## Development Notes
87+
88+
- All packages are built as ES modules with TypeScript declarations
89+
- Use `pnpm nx affected` commands to run tasks only on changed packages
90+
- The repository uses conventional commits and automated releases via changesets
91+
- Individual packages can be tested independently using their specific build/test scripts

e2e/oidc-app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"serve": "pnpm nx nxServe"
1010
},
1111
"dependencies": {
12+
"@forgerock/javascript-sdk": "^4.8.2",
1213
"@forgerock/oidc-client": "workspace:*"
1314
},
1415
"nx": {

e2e/oidc-app/src/main.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,37 @@
1-
console.log('Starting OIDC App...');
1+
import { oidc } from '@forgerock/oidc-client';
2+
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+
},
13+
},
14+
});
15+
16+
// create object from URL query parameters
17+
const urlParams = new URLSearchParams(window.location.search);
18+
const code = urlParams.get('code');
19+
// const state = urlParams.get('state');
20+
// get error and error_description if they exist
21+
const error = urlParams.get('error');
22+
// const errorDescription = urlParams.get('error_description');
23+
24+
if (!code && !error) {
25+
const response = await oidcClient.authorize.background();
26+
27+
if ('error' in response) {
28+
console.error('Authorization Error:', response);
29+
// window.location.assign(response.redirectUrl);
30+
return;
31+
} else if ('code' in response) {
32+
console.log('Authorization Code:', response.code);
33+
}
34+
}
35+
}
36+
37+
app();

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/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
11
# oidc-client
22

33
A generic OpenID Connect (OIDC) client library for JavaScript and TypeScript, designed to work with any OIDC-compliant identity provider.
4+
5+
```js
6+
// Initialize OIDC Client
7+
const oidcClient = oidc({
8+
/* config */
9+
});
10+
11+
// Authorize API
12+
const authResponse = oidcClient.authorize.background(); // Returns code and state if successful, error and Auth URL if not
13+
const authUrl = oidcClient.authorize.url(); // Returns Auth URL or error
14+
15+
// Tokens API
16+
const newTokens = oidcClient.tokens.exchange({
17+
/* code, state */
18+
}); // Returns new tokens or error
19+
const existingTokens = oidcClient.tokens.get(); // Returns existing tokens or error
20+
const revokeResponse = oidcClient.tokens.revoke(); // Returns null or error
21+
const endSessionResponse = oidcClient.tokens.endSession(); // Returns null or error
22+
23+
// User API
24+
const user = oidcClient.user.info(); // Returns user object or error
25+
const logoutResponse = oidcClient.user.logout(); // Returns null or error
26+
```

packages/oidc-client/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@
2626
"test:watch": "pnpm nx nxTest --watch"
2727
},
2828
"dependencies": {
29+
"@forgerock/iframe-manager": "workspace:*",
30+
"@forgerock/sdk-logger": "workspace:*",
31+
"@forgerock/sdk-oidc": "workspace:*",
32+
"@forgerock/sdk-request-middleware": "workspace:*",
2933
"@forgerock/sdk-types": "workspace:*",
30-
"@forgerock/storage": "workspace:*"
34+
"@reduxjs/toolkit": "catalog:",
35+
"effect": "^3.12.7"
3136
},
3237
"nx": {
3338
"tags": ["scope:package"]

packages/oidc-client/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
export * from './lib/token-store.js';
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+
export * from './lib/client.store.js';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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';
9+
import { Micro } from 'effect';
10+
11+
import {
12+
authorizeFetchµ,
13+
createAuthorizeUrlµ,
14+
authorizeIframeµ,
15+
buildAuthorizeOptionsµ,
16+
createAuthorizeErrorµ,
17+
} from './authorize.request.utils.js';
18+
19+
import type { WellKnownResponse } from '@forgerock/sdk-types';
20+
21+
import type { OidcConfig } from './config.types.js';
22+
import { AuthorizeErrorResponse, AuthorizeSuccessResponse } from './authorize.request.types.js';
23+
24+
export async function authorizeµ(
25+
wellknown: WellKnownResponse,
26+
config: OidcConfig,
27+
log: CustomLogger,
28+
options?: GetAuthorizationUrlOptions,
29+
) {
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));
49+
}),
50+
);
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));
65+
}),
66+
);
67+
}
68+
}),
69+
);
70+
}

0 commit comments

Comments
 (0)