Ping JavaScript SDK
First point of contact for building web apps with the Ping Orchestration JavaScript SDK (@forgerock/journey-client, @forgerock/davinci-client, @forgerock/oidc-client). Handles three authentication flows:
- Journey — callback-based auth against PingAM or PingOne AIC → delegates to
ping-orchestration-reactjs-journey-sdk - DaVinci — collector-based auth against PingOne DaVinci → delegates to
ping-orchestration-reactjs-davinci-sdk - OIDC centralized login — OAuth2 authorization code flow (browser redirect) → handled inline
Runs a three-step wizard (W1 → W2 → W3) to determine flow type, detect framework, collect shared config, then hands off or generates code.
Framework Registry
This table is the single source of truth for framework routing. The decision tree references it by Status. To add a new framework: change its status from placeholder to active, add delegate skill names, fill in the OIDC templates directory, and add any build-tool-specific guidance to the "Build Tool Configuration" subsection.
| Framework | Journey Delegate | DaVinci Delegate | OIDC Templates | Status |
|---|---|---|---|---|
| React | ping-orchestration-reactjs-journey-sdk |
ping-orchestration-reactjs-davinci-sdk |
assets/react/ |
active |
| Angular | — | — | assets/angular/ |
placeholder |
| Vue | — | — | assets/vue/ |
placeholder |
| Vanilla JS | — | — | assets/vanilla/ |
placeholder |
Wizard
Run these three steps every time the skill is invoked without a create-sample argument.
W1 — Flow type
Ask:
Which flow are you building?
- Journey — callback-based auth against PingAM or PingOne AIC
- DaVinci — collector-based auth against PingOne DaVinci
- OIDC centralized login — OAuth2/OIDC authorization code flow (browser redirect, no custom UI required)
W1b — Framework detection
Before collecting config, detect or ask about the user's framework.
If a package.json is present, run:
grep -E '"(react|vue|@angular/core|vite)"' package.json
reactfound → React detected → proceed to W2.vueor@angular/corefound → announce placeholder and offer choices:"A dedicated
ping-orchestration-vue-sdk/ping-orchestration-angular-sdkskill is on the way. I can:- Generate framework-neutral SDK wiring — client init, token exchange, and route guard pseudocode you can adapt to your framework's patterns.
- Route to the React skill (
ping-orchestration-reactjs-journey-sdk/ping-orchestration-reactjs-davinci-sdk) as a reference implementation you can port.
Which would you prefer?"
- If the user chooses option 1 → proceed to W2 and then use the Generic (Placeholder Framework) Path below.
- If the user chooses option 2 → proceed to W2 and delegate to the React skill.
react-nativefound (check separately) → announce thatping-orchestration-react-native-sdkis on the way; no stopgap exists.- Nothing found, or no
package.json→ ask: "Which JavaScript framework are you using?" List active frameworks from the Framework Registry. If the user names a placeholder framework, offer the same two options above.
W2 — Shared configuration
Collect the following parameters. Skip any the user has already provided in their message.
| Parameter | Required for | Notes |
|---|---|---|
wellknown |
All flows | Full OIDC discovery URL ending in .well-known/openid-configuration |
clientId |
All flows | OAuth client ID registered in PingOne/AIC |
redirectUri |
All flows | Must match the registered value exactly — include /callback path suffix |
scope |
All flows | Space-separated; must include openid |
appName |
All flows | Used for project/directory naming |
journeyName |
Journey only | Authentication tree name (e.g., Login) |
acrValues |
DaVinci/OIDC only | Optional ACR values string |
Validation before proceeding to W3:
wellknownmust end with.well-known/openid-configurationscopemust includeopenid— if missing, add it and tell the userredirectUrimust be an absolute URL starting withhttp://orhttps://
W3 — Confirm and handoff
Display a summary table:
| Parameter | Value |
|---|---|
| Flow | <journey | davinci | oidc-centralized> |
| Framework | <React> |
| Well-known URL | <wellknown> |
| Client ID | <clientId> |
| Redirect URI | <redirectUri> |
| Scope | <scope> |
| App name | <appName> |
| Journey name (Journey only) | <journeyName> |
| ACR values (DaVinci/OIDC, if provided) | <acrValues> |
Ask: "Does this look right? Shall I proceed?"
On confirmation:
- Journey (active framework) → invoke the Journey delegate from the Framework Registry via the Skill tool. Pass the collected config in your invocation context. Do not inline that skill's content. Note: the Journey delegate uses its own env var naming (
VITE_WELLKNOWN_URL,VITE_WEB_OAUTH_CLIENT, etc.) — the delegate will handle this; pass the logical values from W2. - DaVinci (active framework) → invoke the DaVinci delegate from the Framework Registry via the Skill tool. Pass the collected config. Do not inline that skill's content. Note: the DaVinci delegate uses its own env var naming (
VITE_CLIENT_ID,VITE_DISCOVERY_ENDPOINT, etc.) — pass the logical values from W2. - OIDC centralized (active framework) → read
assets/oidc-centralized-reference.mdand continue to the OIDC Centralized Login section below. Use templates from the framework'sOIDC Templatesdirectory. - Any flow (placeholder framework, generic path) → continue to the Generic (Placeholder Framework) Path section below.
Generic (Placeholder Framework) Path
Use this path when the user's framework is placeholder in the Framework Registry and they chose framework-neutral SDK wiring in W1b. The SDK packages are pure JavaScript with no framework dependency — this path provides the integration wiring without framework-specific component rendering.
What to generate
For each flow type, produce a single integration module the user can adapt to their framework:
Journey:
import { journey, callbackType } from '@forgerock/journey-client';
import { oidc } from '@forgerock/oidc-client';
// 1. Initialise OIDC client (for token management after Journey completes)
const oidcClient = await oidc({ config: {
serverConfig: { wellknown: '<wellknown>' },
clientId: '<clientId>',
redirectUri: '<redirectUri>',
scope: '<scope>',
} });
// 2. Start Journey
const client = await journey({
serverConfig: { wellknown: '<wellknown>' },
journeyName: '<journeyName>',
});
let step = await client.start();
// 3. Loop: render callbacks, collect input, submit
while (step.type === 'Step') {
for (const cb of step.callbacks) {
// Inspect cb.type (callbackType.NameCallback, callbackType.PasswordCallback, etc.)
// Collect user input and call cb.setInput(value) or equivalent setter
}
step = await client.next(step);
}
// 4. Handle outcome
if (step.type === 'SuccessStep') {
// Journey complete — tokens available via oidcClient.token.get()
} else {
// step.type === 'FailureStep' — display error
}
DaVinci:
import { davinci } from '@forgerock/davinci-client';
const client = await davinci({
serverConfig: { wellknown: '<wellknown>' },
clientId: '<clientId>',
redirectUri: '<redirectUri>',
scope: '<scope>',
});
let node = await client.start();
// Loop: render collectors, collect input, submit
while (node.status === 'continue') {
const collectors = node.client.collectors;
for (const collector of collectors) {
// Inspect collector.type (TextCollector, PasswordCollector, SubmitCollector, etc.)
// Set value: collector.value = userInput;
}
node = await client.next();
}
if (node.status === 'success') {
// Auth complete — session established
} else {
// node.status === 'error' — display node.error
}
OIDC centralized:
import { oidc } from '@forgerock/oidc-client';
const CONFIG = {
serverConfig: { wellknown: '<wellknown>' },
clientId: '<clientId>',
redirectUri: '<redirectUri>',
scope: '<scope>',
};
const client = await oidc({ config: CONFIG });
// Login: redirect to authorization endpoint
const url = await client.authorize.url();
window.location.href = url;
// Callback page: exchange code for tokens
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
if (code && state) {
await client.token.exchange(code, state);
window.location.replace('/');
}
// Check auth state
const tokens = await client.token.get();
// Logout
await client.user.logout();
Guidance to include
After generating the integration module:
- Explain which parts map to framework concerns (the render loop → component tree; the auth check → route guard; the callback page → dedicated route).
- Point to the React delegate skill as a full reference implementation they can study.
- Offer to help wire the module into their specific framework's patterns if they share their app structure.
Shared Setup Guidance
This section applies regardless of flow type or framework. Provide this guidance before delegating or generating code.
SDK setup (all frameworks)
Full package reference: references/sdk-packages.md
Install only the packages your flow needs:
# Journey
npm install @forgerock/journey-client @forgerock/oidc-client
# DaVinci
npm install @forgerock/davinci-client
# Add @forgerock/oidc-client if you need token management beyond DaVinci's built-in session
# OIDC centralized login
npm install @forgerock/oidc-client
# Optional add-ons (any flow)
npm install @forgerock/protect # PingOne Protect / behavioral signals
npm install @forgerock/device-client # Device profile, OATH, Push, WebAuthn management
Common pitfalls (SDK-level)
redirectUrimismatch: the value must match what's registered in PingOne/AIC exactly — trailing slash, port number, and path all matter.- Missing
openidscope:@forgerock/oidc-clientrequiresopenidin scope or token exchange will fail. - CORS: the well-known endpoint must include your app's origin in the server's CORS policy. Check PingOne/AIC CORS settings if you see preflight failures.
wellknownvsserverConfig: the new SDK (@forgerock/journey-clientv2+) useswellknownonly. The legacyserverConfig.baseUrlis not supported.
Build tool configuration
Apply the section below that matches the detected framework. Only one applies per project.
Note for placeholder frameworks (Angular, Vue, Vanilla JS): Full skill support for these frameworks is not yet active. The sections below document the correct build tool conventions so you can configure your environment correctly when using the Generic Placeholder Framework Path. No OIDC templates are generated for placeholder frameworks — adapt the patterns manually.
React + Vite
Env vars use the VITE_ prefix (required by Vite for client-side exposure):
VITE_PING_WELLKNOWN=https://auth.example.com/am/oauth2/alpha/.well-known/openid-configuration
VITE_PING_CLIENT_ID=my-app
VITE_PING_REDIRECT_URI=http://localhost:5173/callback
VITE_PING_SCOPE=openid profile
# Journey only:
VITE_PING_JOURNEY_NAME=Login
# DaVinci/OIDC optional:
VITE_PING_ACR_VALUES=
Reference: assets/react/.env.template
Vite-specific pitfalls:
- Only vars prefixed
VITE_are exposed to the browser bundle. Server-only secrets must stay unprefixed. - Do not put a static
public/callback.htmlwith an inlineoidc().token.exchange()script. When@forgerock/oidc-clientcallsauthorize.background()for silent token renewal, it opens a hidden iframe to the authorization endpoint which redirects back to theredirectUri. If a static script at that URL instantiates a second OIDC client and starts exchanging the code, it hijacks the iframe and causes an iframe timeout error. Instead, handle the callback inside the React app as a normal route (seeoidc-callback.jsx.template).
Angular CLI
Env vars go in src/environments/environment.ts:
export const environment = {
pingWellknown: 'https://auth.example.com/am/oauth2/alpha/.well-known/openid-configuration',
pingClientId: 'my-app',
pingRedirectUri: 'http://localhost:4200/callback',
pingScope: 'openid profile',
// Journey only:
pingJourneyName: 'Login',
};
Angular-specific notes:
- Use
environment.ts/environment.prod.tsfor config — Angular does not exposeprocess.envto the browser. - The callback route is a normal Angular route component — no static HTML workaround needed since Angular's router handles it.
Vue CLI / Vite
Env vars use the prefix matching your build tool:
- Vite →
VITE_prefix (same as React + Vite above) - Vue CLI (webpack) →
VUE_APP_prefix
VUE_APP_PING_WELLKNOWN=https://auth.example.com/am/oauth2/alpha/.well-known/openid-configuration
VUE_APP_PING_CLIENT_ID=my-app
VUE_APP_PING_REDIRECT_URI=http://localhost:8080/callback
VUE_APP_PING_SCOPE=openid profile
Access with process.env.VUE_APP_* (Vue CLI) or import.meta.env.VITE_* (Vite).
Vanilla JS / Other
No framework-specific env conventions. Use any approach that suits the project:
- A
config.jsfile exporting constants .envloaded viadotenvat build time- Inline
<script>variables for zero-build setups
create-sample command
/ping-orchestration-javascript-sdk create-sample "<description>"
Steps:
- Analyse the description to determine flow type (journey / davinci / oidc-centralized) and framework.
- If flow type is ambiguous, ask one clarifying question.
- If framework is not React and is not detectable from the project, ask.
- Collect any missing required parameters from W2 that are not inferable from the description.
- If framework is
activein the Framework Registry:- Journey → invoke the Journey delegate via Skill tool with pre-filled config.
- DaVinci → invoke the DaVinci delegate via Skill tool with pre-filled config.
- OIDC centralized → read
assets/oidc-centralized-reference.mdand generate templates from the framework'sOIDC Templatesdirectory with substituted placeholders.
- If framework is
placeholder→ use the Generic (Placeholder Framework) Path.
Template placeholders (OIDC centralized only):
| Placeholder | Value |
|---|---|
PLACEHOLDER_APP_NAME |
appName |
PLACEHOLDER_WELLKNOWN |
wellknown |
PLACEHOLDER_CLIENT_ID |
clientId |
PLACEHOLDER_REDIRECT_URI |
redirectUri |
PLACEHOLDER_SCOPE |
scope |
PLACEHOLDER_ACR_VALUES |
acrValues — omit the line if not provided |
OIDC Centralized Login
Handled inline — no delegation. Uses @forgerock/oidc-client directly. Full API reference: assets/oidc-centralized-reference.md.
Flow
- User lands on login page → call
client.authorize.url()to generate the authorization URL → redirect the browser. - User authenticates on PingOne/AIC → browser redirected back to
redirectUri. - Callback page reads
codeandstatefrom the URL → callsclient.token.exchange(code, state)→ tokens stored. - App calls
client.token.get()to read tokens → user is authenticated, render protected content. - Background renewal: call
client.token.get({ backgroundRenew: true })to keep the session alive via a hidden iframe. - Logout: call
client.user.logout()→ revokes the access token and ends the session on the server.
Client initialisation
import { oidc } from '@forgerock/oidc-client';
const CONFIG = {
serverConfig: {
wellknown: import.meta.env.VITE_PING_WELLKNOWN,
},
clientId: import.meta.env.VITE_PING_CLIENT_ID,
redirectUri: import.meta.env.VITE_PING_REDIRECT_URI,
scope: import.meta.env.VITE_PING_SCOPE,
// acrValues: import.meta.env.VITE_PING_ACR_VALUES, // uncomment if needed
};
const client = await oidc({ config: CONFIG });
Token exchange (callback page)
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
if (code && state) {
await client.token.exchange(code, state);
window.location.replace('/'); // redirect to home after successful exchange
}
Route guard pattern
The route guard logic is the same for all frameworks — only the component wrapper changes.
Core logic (framework-agnostic):
async function checkAuth(client) {
const tokens = await client.token.get();
if (!tokens) {
const url = await client.authorize.url();
window.location.href = url;
return null;
}
return tokens;
}
React wrapper:
import { useEffect, useState } from 'react';
export function ProtectedRoute({ children }) {
const [isAuthenticated, setIsAuthenticated] = useState(null);
useEffect(() => {
client.token.get().then((tokens) => {
setIsAuthenticated(!!tokens);
});
}, []);
if (isAuthenticated === null) return <div>Loading…</div>;
if (!isAuthenticated) {
client.authorize.url().then((url) => { window.location.href = url; });
return null;
}
return children;
}
For other frameworks, adapt the wrapper to the framework's guard mechanism (Angular CanActivate, Vue beforeEach navigation guard, etc.) using the same checkAuth logic.
Templates
Generate these files when flow type is oidc-centralized. Read templates from the active framework's subdirectory in the Framework Registry's OIDC Templates column (e.g., assets/react/ for React). Substitute placeholders and write to the user's project directory.
React (assets/react/):
| Template file | Output path | Purpose |
|---|---|---|
assets/react/.env.template |
.env |
Environment variables (Vite VITE_ prefix) |
assets/react/oidc-app.jsx.template |
src/App.jsx |
App shell with OIDC context + protected route |
assets/react/oidc-login.jsx.template |
src/pages/Login.jsx |
Login trigger (authorize.url() → redirect) |
assets/react/oidc-callback.jsx.template |
src/pages/Callback.jsx |
Code exchange handler (token.exchange via React component) |
assets/react/oidc-home.jsx.template |
src/pages/Home.jsx |
Home page with sign-in CTA and user info + sign-out |
Other frameworks: When their OIDC Templates directory is populated, follow the same pattern — read from the framework's assets/<framework>/ directory.
Common pitfalls (OIDC)
statemismatch: never construct the authorization URL manually. Always useclient.authorize.url()— it generates and stores thestateandcode_verifier(PKCE) automatically. Callingtoken.exchangewith astatethat doesn't match stored state throwsstate_mismatch.- Background renewal requires iframe allowance:
backgroundRenew: trueopens a hidden iframe to the authorization endpoint. If your CSP blocksframe-src, renewal will silently fail. Add your PingOne/AIC domain toframe-src. - PKCE is always on:
@forgerock/oidc-clientuses PKCE by default and does not expose a toggle. Your OAuth client registration must have PKCE enabled (or set to optional).