This guide documents the conversion from @forgerock/javascript-sdk (legacy) to the newer Ping SDK packages. For the complete API-level mapping of every class, method, parameter, and return type, see interface_mapping.md.
| Legacy | New | Purpose |
|---|---|---|
@forgerock/javascript-sdk — FRAuth class |
@forgerock/journey-client |
Authentication tree/journey flows |
@forgerock/javascript-sdk — TokenManager class |
@forgerock/oidc-client |
OAuth2/OIDC token management, user info, logout |
@forgerock/javascript-sdk — FRDevice* classes |
@forgerock/device-client |
Device profile & management (OATH, Push, WebAuthn, Bound, Profile) |
@forgerock/ping-protect |
@forgerock/protect |
PingOne Protect/Signals integration |
The legacy SDK uses a global static Config.set(). The new SDK uses async factory functions that each return an independent client instance.
| Legacy | New | Notes |
|---|---|---|
import { Config } from '@forgerock/javascript-sdk' |
import { journey } from '@forgerock/journey-client' |
Journey client factory |
| — | import { oidc } from '@forgerock/oidc-client' |
OIDC client factory (separate package) |
Config.set({ clientId, redirectUri, scope, serverConfig, realmPath, tree }) |
const journeyClient = await journey({ config }) |
Async initialization; config per-client, not global |
TokenStorage.get() |
const tokens = await oidcClient.token.get() |
Now part of oidcClient; check errors with if ('error' in tokens) |
Both clients require only the OIDC wellknown endpoint URL. All other configuration (baseUrl, paths, realm) is derived automatically:
// Shared config — both clients can use the same base config
const config = {
serverConfig: {
wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration',
},
clientId: 'my-app',
redirectUri: `${window.location.origin}/callback`,
scope: 'openid profile',
};
// Journey client — for authentication tree flows
// (accepts but ignores clientId, redirectUri, scope with a console warning)
const journeyClient = await journey({ config });
// OIDC client — for token management, user info, logout
// (requires clientId, redirectUri, scope)
const oidcClient = await oidc({ config });| Legacy Method | New Method | Notes |
|---|---|---|
FRAuth.start({ tree: 'Login' }) |
journeyClient.start({ journey: 'Login' }) |
Tree name passed per-call via journey param (not in config) |
FRAuth.next(step, { tree: 'Login' }) |
journeyClient.next(step) |
Tree is set at start(), not repeated on next() |
FRAuth.redirect(step) |
await journeyClient.redirect(step) |
Now async. Step stored in sessionStorage (was localStorage) |
FRAuth.resume(resumeUrl) |
await journeyClient.resume(resumeUrl) |
Previous step retrieved from sessionStorage (was localStorage) |
SessionManager.logout() |
await journeyClient.terminate() |
Ends the AM session via /sessions endpoint |
Legacy:
import { FRAuth, StepType } from '@forgerock/javascript-sdk';
Config.set({
serverConfig: { baseUrl: 'https://am.example.com/am' },
realmPath: 'alpha',
tree: 'Login',
});
let step = await FRAuth.start();
while (step.type === StepType.Step) {
// ... handle callbacks ...
step = await FRAuth.next(step);
}
if (step.type === StepType.LoginSuccess) {
console.log('Session token:', step.getSessionToken());
}New:
import { journey } from '@forgerock/journey-client';
const journeyClient = await journey({
config: {
serverConfig: {
wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration',
},
},
});
let result = await journeyClient.start({ journey: 'Login' });
while (result.type === 'Step') {
// ... handle callbacks ...
result = await journeyClient.next(result);
}
if ('error' in result) {
console.error('Journey error:', result);
} else if (result.type === 'LoginSuccess') {
console.log('Session token:', result.getSessionToken());
}| Legacy | New | Notes |
|---|---|---|
UserManager.getCurrentUser() |
await oidcClient.user.info() |
Returns typed UserInfoResponse or GenericError |
FRUser.logout({ logoutRedirectUri }) |
await oidcClient.user.logout() |
Revokes tokens + ends session. Returns structured result |
| Legacy | New | Notes |
|---|---|---|
TokenManager.getTokens({ forceRenew: true }) |
await oidcClient.token.get({ forceRenew: true, backgroundRenew: true }) |
Single call with auto-renewal. Returns tokens or error |
OAuth2Client.getAuthCodeByIframe() then TokenManager.getTokens({ authorizationCode, ...params }) |
await oidcClient.authorize.background() then await oidcClient.token.exchange(code, state) |
Two-step when you need explicit control over authorize + exchange |
TokenManager.deleteTokens() |
await oidcClient.token.revoke() |
Revokes remotely AND deletes locally |
TokenStorage.get() |
await oidcClient.token.get() |
Auto-retrieves from storage; check 'error' in tokens |
TokenStorage.set(tokens) |
Handled automatically by oidcClient.token.exchange() |
Tokens stored after exchange |
TokenStorage.remove() |
await oidcClient.token.revoke() |
Combined revoke + delete |
| Legacy | New | Change |
|---|---|---|
Tokens { accessToken?, idToken?, refreshToken?, tokenExpiry? } |
OauthTokens { accessToken, idToken, refreshToken?, expiresAt?, expiryTimestamp? } |
accessToken and idToken now required. tokenExpiry renamed to expiryTimestamp |
| Legacy | New | Notes |
|---|---|---|
import { CallbackType } from '@forgerock/javascript-sdk' |
import { callbackType } from '@forgerock/journey-client' |
PascalCase enum → camelCase object |
CallbackType.RedirectCallback |
callbackType.RedirectCallback |
Values remain the same strings |
import { FRCallback } from '@forgerock/javascript-sdk' |
import { BaseCallback } from '@forgerock/journey-client/types' |
Base class renamed |
import { NameCallback, ... } from '@forgerock/javascript-sdk' |
import { NameCallback, ... } from '@forgerock/journey-client/types' |
Same class names, different import path |
import { FRWebAuthn, WebAuthnStepType } from '@forgerock/javascript-sdk' |
import { WebAuthn, WebAuthnStepType } from '@forgerock/journey-client/webauthn' |
FRWebAuthn → WebAuthn, submodule import |
For the full type-level mapping see interface_mapping.md § Authentication Flow. The three implementation pain points that aren't obvious from the type table:
1. tree is no longer threaded through next().
The legacy pattern merged initOptions and nextOptions and passed the combined object to both start() and next(), carrying tree through every call. In the new SDK, next() does not accept a journey name — it only takes { query? }. Remove any options merging/forwarding from your next() wrapper.
// Legacy — options object merged and threaded through every call
const options = { ...initOptions, ...nextOptions };
await FRAuth.next(prevStep, options); // tree still in options
// New — next() has no journey param; don't merge options through
await journeyClient.next(prevStep, { query: nextOptions?.query });2. A journey stack stored StepOptions[]; it now stores StartParam[].
If you maintain a stack for switching between trees, the stored type changes from StepOptions to StartParam and the deduplication key changes from tree to journey:
// Legacy
if (options?.tree !== current[current.length - 1]?.tree) { ... }
// New
if (options?.journey !== current[current.length - 1]?.journey) { ... }| Legacy | New | Notes |
|---|---|---|
FRStep (class instance) |
JourneyStep (object type) |
Cannot use instanceof; use result.type === 'Step' |
FRLoginSuccess (class instance) |
JourneyLoginSuccess (object type) |
Use result.type === 'LoginSuccess' |
FRLoginFailure (class instance) |
JourneyLoginFailure (object type) |
Use result.type === 'LoginFailure' |
All step methods (getCallbackOfType, getDescription, getHeader, getStage, getSessionToken, etc.) remain identical.
The legacy SDK created step objects by instantiating new FRStep(payload), new FRLoginSuccess(payload), or new FRLoginFailure(payload). The new SDK creates these internally — there are no classes to instantiate. Because all three types are plain objects, replace every new FR* call with an object literal typed against the corresponding interface:
// Before
import { FRStep, FRLoginSuccess, FRLoginFailure } from '@forgerock/javascript-sdk';
const step = new FRStep(payload);
const success = new FRLoginSuccess(payload);
const failure = new FRLoginFailure(payload);
// After
import type {
JourneyStep,
JourneyLoginSuccess,
JourneyLoginFailure,
} from '@forgerock/journey-client/types';
import { StepType, createCallback } from '@forgerock/journey-client/types';
const mockStep: JourneyStep = {
type: StepType.Step,
payload: rawPayload,
callbacks: rawPayload.callbacks?.map(createCallback) ?? [],
getCallbackOfType: (type) => {
throw new Error('not implemented');
},
getCallbacksOfType: (type) => [],
setCallbackValue: () => {},
getDescription: () => undefined,
getHeader: () => undefined,
getStage: () => undefined,
};
const mockSuccess: JourneyLoginSuccess = {
type: StepType.LoginSuccess,
payload: rawPayload,
getRealm: () => 'alpha',
getSessionToken: () => 'abc123',
getSuccessUrl: () => undefined,
};
const mockFailure: JourneyLoginFailure = {
type: StepType.LoginFailure,
payload: rawPayload,
getCode: () => 401,
getDetail: () => undefined,
getMessage: () => 'Login failure',
getReason: () => undefined,
getProcessedMessage: () => [],
};The legacy HttpClient is removed. It provided auto bearer-token injection, 401 token refresh, and policy advice handling. In the new SDK, manage tokens manually:
const tokens = await oidcClient.token.get({ backgroundRenew: true });
if ('error' in tokens) {
throw new Error('No valid tokens');
}
const response = await fetch('https://api.example.com/resource', {
method: 'GET',
headers: { Authorization: `Bearer ${tokens.accessToken}` },
});Client initialization (factory functions like journey() and oidc()) will throw when misconfigured. However, once initialized, client methods return error objects instead of throwing exceptions. Some journey-client methods may still throw in certain edge cases (known tech debt). Defensive code should handle both patterns:
| Legacy | New |
|---|---|
try { ... } catch (err) { console.error(err) } |
if ('error' in result) { console.error(result.error, result.message) } |
Legacy:
try {
const user = await UserManager.getCurrentUser();
setUser(user);
} catch (err) {
console.error(`Error: get current user; ${err}`);
setUser({});
}New:
const user = await oidcClient.user.info();
if ('error' in user) {
console.error('Error getting user:', user);
setUser({});
} else {
setUser(user);
}- Async-First Initialization: SDK clients initialized asynchronously with factory functions (
journey(),oidc()) - Instance-Based APIs: Methods called on client instances, not static classes
- Separated Packages: Journey auth (
@forgerock/journey-client), OIDC (@forgerock/oidc-client), Device (@forgerock/device-client), Protect (@forgerock/protect) - Explicit Error Handling: Response objects contain
{ error }property instead of throwing exceptions - Wellknown-Based Discovery: Only
serverConfig.wellknownis required; all paths derived automatically - No Built-in HTTP Client: Protected API requests require manual token retrieval and header management
- Config Fixed at Creation: Per-call config overrides removed; create separate client instances for different configs
For the complete API-level mapping (every class, method, parameter change, return type change, and behavioral note), see interface_mapping.md.