@ping-identity/rn-journey
v1.0.0
Published
Ping Identity Journey for React Native
Downloads
266
Readme
Ping Identity React Native Journey
This module exposes native-backed Journey clients for Android and iOS.
Integrating the SDK into your project
Note: This module requires that the
@ping-identity/rn-coremodule is already set up and installed.
# Install & setup the core module
yarn add @ping-identity/rn-core
# Install the rn-journey module
yarn add @ping-identity/rn-journey
# If you are developing your app using iOS, run this command
cd ios && pod installOptional integration packages:
yarn add @ping-identity/rn-storage
yarn add @ping-identity/rn-loggerHow to Use the SDK
Step 1: Create a minimal client
Use this baseline configuration first.
import { createJourneyClient } from '@ping-identity/rn-journey';
const client = createJourneyClient({
serverUrl: 'https://example.com/am',
realm: 'alpha',
cookie: 'iPlanetDirectoryPro',
timeout: 30000,
});Step 2: Add OIDC module (with optional storage)
Add modules.oidc when your Journey flow needs OIDC token/session operations (for example
user(), refresh(), userinfo(), and revoke()).
Storage inside modules.oidc.storage and modules.session.storage is optional. Configure it only
if you need native-backed persistence; otherwise omit storage values.
import { createJourneyClient } from '@ping-identity/rn-journey';
import {
CacheStrategy,
configureOidcStorage,
configureSessionStorage,
} from '@ping-identity/rn-storage';
const sessionStorage = configureSessionStorage({
android: {
fileName: 'journey-session',
keyAlias: 'journey-session',
strongBoxPreferred: true,
cacheStrategy: 'cache_on_failure',
},
ios: {
account: 'com.example.app.session',
encryptor: true,
cacheable: false,
},
});
const oidcStorage = configureOidcStorage({
android: {
fileName: 'journey-oidc',
keyAlias: 'journey-oidc',
strongBoxPreferred: true,
cacheStrategy: CacheStrategy.CACHE_ON_FAILURE,
},
ios: {
account: 'com.example.app.oidc',
encryptor: true,
cacheable: false,
},
});
const client = createJourneyClient({
serverUrl: 'https://example.com/am',
realm: 'alpha',
cookie: 'iPlanetDirectoryPro',
timeout: 30000,
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://example.com/am/oauth2/alpha/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
scopes: ['openid', 'profile', 'email'],
storage: oidcStorage,
},
session: {
storage: sessionStorage,
},
},
});Pass optional integrations through config.modules. The JS API is createJourneyClient(config).
When provided, storage handles in modules.session.storage and modules.oidc.storage must come
from configureSessionStorage(...) / configureOidcStorage(...).
Step 3: Add logging integration (optional)
If you install the logger package, pass a JS logger instance created via
@ping-identity/rn-logger.
If the logger package is not installed/configured, do not pass logger values in Journey config.
import { createJourneyClient } from '@ping-identity/rn-journey';
import { logger } from '@ping-identity/rn-logger';
const jsLogger = logger({ level: 'debug' });
const client = createJourneyClient({
serverUrl: 'https://example.com/am',
logger: jsLogger,
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://example.com/am/oauth2/alpha/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
scopes: ['openid'],
},
},
});Drive the Journey imperatively
const firstNode = await client.start('Login');
const nextNode = await client.next({
callbacks: [
{ type: 'NameCallback', value: 'demo-user' },
{ type: 'PasswordCallback', value: 'demo-password' },
],
});
const resumedNode = await client.resume('com.example.app://callback?code=123');
const session = await client.user();
const refreshedSession = await client.refresh();
const userInfo = await client.userinfo();
const ssoToken = await client.ssoToken();
await client.revoke();
await client.logoutUser();
await client.dispose();useJourney does not auto-advance nodes. Progression policy is app-controlled via explicit next(...) calls.
Start options are supported when initiating a journey:
const node = await client.start('Login', {
forceAuth: true,
noSession: true,
});Handle node states explicitly in your UI flow:
const node = await client.start('Login');
switch (node.type) {
case 'ContinueNode':
await client.next({
callbacks: [{ type: 'NameCallback', value: 'demo-user' }],
});
break;
case 'ErrorNode':
console.log(node.message);
break;
case 'FailureNode':
console.log(node.cause ?? node.message);
break;
case 'SuccessNode':
console.log('Authenticated');
break;
}Post Authentication Operations
After a Journey login succeeds, use the following operations to inspect and manage the active user session:
const userSession = await client.user();
const ssoToken = await client.ssoToken();
if (userSession) {
const refreshedSession = await client.refresh();
const userInfo = await client.userinfo();
await client.revoke();
}
await client.logoutUser();user()returns token/session payload (accessToken, optionalrefreshToken,expiresIn, optionaluserInfo).ssoToken()returns Journey SSO session payload (value,successUrl,realm) when available.refresh()refreshes token payload for the active user.userinfo()fetches user claims for the active user.revoke()revokes access/refresh tokens for the active user.logoutUser()signs out and clears the active Journey user session.
Use the React hook
import { useJourney } from '@ping-identity/rn-journey';
const [node, actions] = useJourney(client);
await actions.start('Login');
if (node?.type === 'ContinueNode') {
await actions.next({
callbacks: [{ type: 'NameCallback', value: 'demo-user' }],
});
}Share Journey state across multiple screens (optional)
import { JourneyProvider, useJourney } from '@ping-identity/rn-journey';
function App(): React.ReactElement {
return (
<JourneyProvider client={client}>
<AuthNavigator />
</JourneyProvider>
);
}
function LoginScreen(): React.ReactElement {
const [node, actions] = useJourney();
return <></>;
}Render callbacks with useJourneyForm
import { callbackType } from '@ping-identity/rn-types';
import { useJourney, useJourneyForm } from '@ping-identity/rn-journey';
const [node, actions] = useJourney(client);
const form = useJourneyForm(node);
const usernameField = form.getFieldByType(callbackType.NameCallback);
const passwordField = form.getFieldByType(callbackType.PasswordCallback);
form.setValueByType(callbackType.NameCallback, 'demo-user');
form.setValueByType(callbackType.PasswordCallback, 'demo-password');
if (form.canSubmit) {
await actions.next(form.input);
}useJourneyForm is headless. It manages normalized fields and submit planning, but does not render UI and does not auto-run callbacks.
Each normalized field includes executionMode and requiresUserInput.
| executionMode | Meaning | requiresUserInput default |
| ---------------------- | ----------------------------------------------------------------- | --------------------------- |
| manual | Callback value is submitted from form/planned input. | true |
| auto_capable | Callback can be executed by app auto-progress policy. | false |
| integration_required | Callback family needs extra native/app integration before submit. | false |
| output_only | Display/system callback, no input value expected. | false |
| unsupported | Callback is not currently handled by helper submit logic. | false |
requiresUserInput exceptions:
| Callback Type | executionMode | requiresUserInput | Reason |
| --------------------- | --------------- | ------------------- | ---------------------------------------------------------------------------------------- |
| HiddenValueCallback | manual | false | Hidden payload should pass through submit planning without forcing a visible input step. |
TODO(test-runner app)(TODO-SEPARATE-TICKET): add Journey integration and E2E tests (including
SuspendedTextOutputCallbackdeep link/email resume flow) once the test-runner app is set up.
Core callback support
The following AM core callbacks are supported on Android and iOS:
| Callback Type | Description | Input Handling |
| --------------------------------- | ------------------------------------------------------------ | -------------- |
| BooleanAttributeInputCallback | Collects true or false input. | Manual input |
| ChoiceCallback | Collects a single selection from available choices. | Manual input |
| ConfirmationCallback | Collects one selected option from a list. | Manual input |
| ConsentMappingCallback | Prompts the user to consent to sharing profile data. | Manual input |
| HiddenValueCallback | Carries non-visual form values. | Manual input |
| KbaCreateCallback | Collects knowledge-based question and answer values. | Manual input |
| MetadataCallback | Injects key-value metadata into the flow. | Output-only |
| NameCallback | Collects a username. | Manual input |
| NumberAttributeInputCallback | Collects a numeric value. | Manual input |
| PasswordCallback | Collects a password or OTP value. | Manual input |
| PollingWaitCallback | Instructs the client to wait and resubmit later. | Output-only |
| StringAttributeInputCallback | Collects string attribute values. | Manual input |
| SuspendedTextOutputCallback | Pauses flow and resumes through external callback/deep link. | Output-only |
| TermsAndConditionsCallback | Collects acceptance of configured terms and conditions. | Manual input |
| TextInputCallback | Collects arbitrary text input. | Manual input |
| TextOutputCallback | Provides display-only message content. | Output-only |
| ValidatedCreatePasswordCallback | Collects password input with policy validation. | Manual input |
| ValidatedCreateUsernameCallback | Collects username input with policy validation. | Manual input |
Integration-dependent families (for example, device profile, FIDO, PingOne Protect, redirect/IdP, and ReCaptcha callbacks) are surfaced in node payloads and require client-side integration before submission (executionMode: 'integration_required').
For FIDO callbacks, run explicit Journey-scoped APIs from @ping-identity/rn-fido (registerForJourney(...) / authenticateForJourney(...)) and then call next(...).
Native parity contract
Journey FIDO callback handling is standardized across Android and iOS with the following native contract:
| Area | Standardized behavior |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Callback type resolution | Runtime native callback class is authoritative; raw callback JSON type is fallback only. |
| Callback execution ownership | FIDO callback execution is owned by @ping-identity/rn-fido Journey-scoped APIs, not next(...) callback mutations. |
| Hidden callback ownership | Journey/native callback execution remains the source of truth for callback state; helper layer does not force custom override values. |
Error handling
All promise rejections throw a JourneyError instance, which extends PingError extends Error.
Use instanceof to narrow the error type:
import { JourneyError } from '@ping-identity/rn-journey';
try {
await client.start('Login');
} catch (err) {
if (err instanceof JourneyError) {
console.log(err.code, err.type, err.message);
}
}Stable Journey error codes:
JOURNEY_CONFIG_ERRORJOURNEY_INIT_ERRORJOURNEY_START_ERRORJOURNEY_NEXT_ERRORJOURNEY_RESUME_ERRORJOURNEY_USER_ERRORJOURNEY_LOGOUT_ERRORJOURNEY_DISPOSE_ERRORJOURNEY_STATE_ERRORJOURNEY_CALLBACK_APPLY_ERRORJOURNEY_UNSUPPORTED_CALLBACK_ERRORJOURNEY_MISSING_INTEGRATION_ERROR
License
This project is licensed under the MIT License - see the LICENSE file for details
