Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/big-kids-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@forgerock/oidc-client': minor
'@forgerock/sdk-oidc': minor
'@forgerock/sdk-types': minor
---

Added RAR support through `authorizationDetails` parameter in OIDC config. Works with or without PAR.
2 changes: 2 additions & 0 deletions e2e/oidc-app/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ <h2>OIDC Client E2E Test Index | Ping Identity JavaScript SDK</h2>
<a href="/ping-am/">Ping AM</a>
<a href="/ping-one/">Ping One</a>
<a href="/par/">PAR (Pushed Authorization Request)</a>
<a href="/rar/">RAR (Rich Authorization Request)</a>
<a href="/rar-par/">RAR with PAR</a>
</div>
</div>
<script type="module" src="index.ts"></script>
Expand Down
46 changes: 4 additions & 42 deletions e2e/oidc-app/src/par/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,27 @@
* of the MIT license. See the LICENSE file for details.
*
*/
import { runLoginJourney } from '../utils/journey-login.js';
import { oidcApp } from '../utils/oidc-app.js';

const AM_BASE = 'https://openam-sdks.forgeblocks.com/am';
const REALM = 'alpha';

const urlParams = new URLSearchParams(window.location.search);
const wellknown = urlParams.get('wellknown');
const AM_BASE = 'https://openam-sdks.forgeblocks.com/am';

const config = {
clientId: 'ParClient',
redirectUri: 'http://localhost:8443/par/',
scope: 'openid profile email',
par: true,
serverConfig: {
wellknown:
wellknown ||
'https://openam-sdks.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
wellknown: `${AM_BASE}/oauth2/alpha/.well-known/openid-configuration`,
},
};

// Run journey Login to establish an AM session before background PAR auth
async function runLoginJourney(username: string, password: string): Promise<void> {
const authenticateUrl = `${AM_BASE}/json/realms/root/realms/${REALM}/authenticate?authIndexType=service&authIndexValue=Login`;

// Step 1: start the journey
const initRes = await fetch(authenticateUrl, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json', 'Accept-API-Version': 'resource=2.1' },
body: '{}',
});
const initJson = await initRes.json();

if (initJson.successUrl) return; // already authenticated

// Fill NameCallback + PasswordCallback
for (const cb of initJson.callbacks ?? []) {
if (cb.type === 'NameCallback') cb.input[0].value = username;
if (cb.type === 'PasswordCallback') cb.input[0].value = password;
}

// Step 2: submit credentials
const submitRes = await fetch(authenticateUrl, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json', 'Accept-API-Version': 'resource=2.1' },
body: JSON.stringify(initJson),
});
const submitJson = await submitRes.json();

if (!submitJson.tokenId && !submitJson.successUrl) {
throw new Error(submitJson.message || 'Login failed');
}
}

const journeyForm = document.getElementById('journey-form') as HTMLFormElement;
const journeyStatus = document.getElementById('journey-status') as HTMLParagraphElement;
const backgroundBtn = document.getElementById('login-background') as HTMLButtonElement;

// Run journey Login to establish an AM session before background PAR auth
journeyForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = (document.getElementById('username') as HTMLInputElement).value;
Expand Down
61 changes: 61 additions & 0 deletions e2e/oidc-app/src/rar-par/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>E2E Test | Ping Identity JavaScript SDK</title>

<style>
#logout {
display: none;
}
#user-info-btn {
display: none;
}
fieldset {
display: inline-flex;
flex-direction: column;
gap: 0.4rem;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<div id="app">
<a href="/">Home</a>
<h1>OIDC App | RAR (Rich Authorization Request) + PAR Login</h1>
<p>
Client: <code>rar</code> &mdash; RAR enabled. <code>authorization_details</code> are
serialized and sent in the authorize url or PAR POST body.
</p>

<h2>Step 1: Establish AM Session (Journey: Login)</h2>
<p>
Background PAR + RAR auth requires an existing AM session. Log in via the Login journey
first.
</p>
<form id="journey-form">
<fieldset>
<label for="username">User Name</label>
<input id="username" type="text" autocomplete="username" />
<label for="password">Password</label>
<input id="password" type="password" autocomplete="current-password" />
<button type="submit">Login (Journey)</button>
</fieldset>
</form>
<p id="journey-status"></p>

<h2>Step 2: RAR OAuth</h2>
<button id="login-background" disabled>Login (Background &mdash; PAR + RAR)</button>
<button id="login-redirect">Login (Redirect &mdash; PAR + RAR)</button>
<button id="get-tokens">Get Tokens (Local)</button>
<button id="get-tokens-background">Get Tokens (Background)</button>
<button id="renew-tokens">Renew Tokens</button>
<button id="logout">Logout</button>
<button id="user-info-btn">User Info</button>
<button id="revoke">Revoke Token</button>
<a href="/rar-par/">Start Over</a>
</div>
<script type="module" src="./main.ts"></script>
</body>
</html>
52 changes: 52 additions & 0 deletions e2e/oidc-app/src/rar-par/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
*
* Copyright (c) 2026 Ping Identity Corporation. All right reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*
*/
import { runLoginJourney } from '../utils/journey-login.js';
import { oidcApp } from '../utils/oidc-app.js';

const urlParams = new URLSearchParams(window.location.search);
const AM_BASE = 'https://openam-stoyan.forgeblocks.com/am';

const config = {
clientId: 'rar',
redirectUri: 'http://localhost:8443/rar-par/',
scope: 'openid profile email',
serverConfig: {
wellknown: `${AM_BASE}/oauth2/alpha/.well-known/openid-configuration`,
},
par: true,
authorizationDetails: [
{
type: 'account_information',
actions: ['list_accounts', 'read_balances'],
locations: ['https://example.com/accounts'],
datatypes: ['balances'],
},
],
};

const journeyForm = document.getElementById('journey-form') as HTMLFormElement;
const journeyStatus = document.getElementById('journey-status') as HTMLParagraphElement;
const backgroundBtn = document.getElementById('login-background') as HTMLButtonElement;

// Run journey Login to establish an AM session before background auth
journeyForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = (document.getElementById('username') as HTMLInputElement).value;
const password = (document.getElementById('password') as HTMLInputElement).value;
journeyStatus.textContent = 'Logging in…';
try {
await runLoginJourney(username, password, AM_BASE);
journeyStatus.textContent = '✓ Session established — background login now available.';
backgroundBtn.disabled = false;
} catch (err) {
journeyStatus.textContent = `✗ ${err instanceof Error ? err.message : 'Login failed'}`;
}
});

oidcApp({ config, urlParams });
28 changes: 28 additions & 0 deletions e2e/oidc-app/src/rar/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!doctype html>
<html>
<head>
<title>E2E Test | Ping Identity JavaScript SDK</title>

<style>
#logout {
display: none;
}
</style>
</head>
<body>
<div id="app">
<a href="/">Home</a>
<h1>OIDC App | RAR Login</h1>
<button id="login-background">Login (Background)</button>
<button id="login-redirect">Login (Redirect)</button>
<button id="get-tokens">Get Tokens (Local)</button>
<button id="get-tokens-background">Get Tokens (Background)</button>
<button id="renew-tokens">Renew Tokens</button>
<button id="logout">Logout</button>
<button id="user-info-btn">User Info</button>
<button id="revoke">Revoke Token</button>
<a href="/rar/">Start Over</a>
</div>
<script type="module" src="./main.ts"></script>
</body>
</html>
31 changes: 31 additions & 0 deletions e2e/oidc-app/src/rar/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* Copyright (c) 2026 Ping Identity Corporation. All right reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*
*/
import { oidcApp } from '../utils/oidc-app.js';

const urlParams = new URLSearchParams(window.location.search);

const config = {
clientId: 'rar',
redirectUri: 'http://localhost:8443/rar/',
scope: 'openid profile email',
serverConfig: {
wellknown:
'https://openam-stoyan.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
},
authorizationDetails: [
{
type: 'account_information',
actions: ['list_accounts', 'read_balances'],
locations: ['https://example.com/accounts'],
datatypes: ['balances'],
},
],
};

oidcApp({ config, urlParams });
53 changes: 53 additions & 0 deletions e2e/oidc-app/src/utils/journey-login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
*
* Copyright (c) 2026 Ping Identity Corporation. All right reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*
*/
const urlParams = new URLSearchParams(window.location.search);
const journeyParam = urlParams.get('journey');

const AM_BASE = 'https://openam-sdks.forgeblocks.com/am';
const REALM = 'alpha';
const JOURNEY = journeyParam || 'Login';

// Run journey Login to establish an AM session before background auth
export async function runLoginJourney(
username: string,
password: string,
amBase?: string,
): Promise<void> {
const authenticateUrl = `${amBase || AM_BASE}/json/realms/root/realms/${REALM}/authenticate?authIndexType=service&authIndexValue=${JOURNEY}`;

// Step 1: start the journey
const initRes = await fetch(authenticateUrl, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json', 'Accept-API-Version': 'resource=2.1' },
body: '{}',
});
const initJson = await initRes.json();

if (initJson.successUrl) return; // already authenticated

// Fill NameCallback + PasswordCallback
for (const cb of initJson.callbacks ?? []) {
if (cb.type === 'NameCallback') cb.input[0].value = username;
if (cb.type === 'PasswordCallback') cb.input[0].value = password;
}

// Step 2: submit credentials
const submitRes = await fetch(authenticateUrl, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json', 'Accept-API-Version': 'resource=2.1' },
body: JSON.stringify(initJson),
});
const submitJson = await submitRes.json();

if (!submitJson.tokenId && !submitJson.successUrl) {
throw new Error(submitJson.message || 'Login failed');
}
}
4 changes: 3 additions & 1 deletion e2e/oidc-suites/src/par.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { expect, test } from '@playwright/test';
import { asyncEvents } from './utils/async-events.js';
import { pingAmPassword, pingAmUsername } from './utils/demo-users.js';

async function loginJourney(page, username: string, password: string) {
import type { Page } from '@playwright/test';

async function loginJourney(page: Page, username: string, password: string) {
await page.getByLabel('User Name').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Login (Journey)' }).click();
Expand Down
Loading
Loading