ping-identity
    Preparing search index...

    Ping Identity

    Ping Identity React Native Journey

    This module exposes native-backed Journey clients for Android and iOS.

    Note: This module requires that the @ping-identity/rn-core module 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 install

    Optional integration packages:

    yarn add @ping-identity/rn-storage
    yarn add @ping-identity/rn-logger

    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,
    });

    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.

    Set modules.oidc.par to true to enable the Pushed Authorization Request flow. The native SDK reads the PAR endpoint from the provider's OIDC discovery document. To override it, set modules.oidc.openId.pushedAuthorizationRequestEndpoint.

    Warning: If the provider's discovery document does not advertise a PAR endpoint and no override is set, the native SDK sends the authorization request to an empty URL instead of failing fast. Confirm PAR support in your provider's discovery document, or set pushedAuthorizationRequestEndpoint explicitly, before enabling par: true.

    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'],
    par: true,
    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(...).

    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'],
    },
    },
    });
    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;
    }

    When this device is acting as the approving device in an RFC 8628 device authorization grant, pass the verification_uri_complete URL from the device authorization response as the verificationUri start option of the useJourney hook. After the Journey flow authenticates the user, the native SDK extracts the user_code from that URL and approves the requesting device.

    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, optional refreshToken, expiresIn, optional userInfo).
    • 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.
    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' }],
    });
    }

    Start options are supported when initiating a journey:

    const node = await actions.start('Login', {
    forceAuth: true,
    noSession: true,
    });

    To approve an RFC 8628 device authorization grant, pass the verificationUri start option (see Approving a device authorization grant):

    await actions.start('Login', {
    verificationUri: 'https://example.com/device?user_code=WDJB-MJHT',
    });

    After the Journey flow authenticates the user, the native SDK extracts the user_code from that URL and approves the requesting device automatically; no extra submit step is required in the app.

    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 <></>;
    }
    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 SuspendedTextOutputCallback deep link/email resume flow) once the test-runner app is set up.

    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(...).

    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.

    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_ERROR
    • JOURNEY_INIT_ERROR
    • JOURNEY_START_ERROR
    • JOURNEY_NEXT_ERROR
    • JOURNEY_RESUME_ERROR
    • JOURNEY_USER_ERROR
    • JOURNEY_LOGOUT_ERROR
    • JOURNEY_DISPOSE_ERROR
    • JOURNEY_STATE_ERROR
    • JOURNEY_CALLBACK_APPLY_ERROR
    • JOURNEY_UNSUPPORTED_CALLBACK_ERROR
    • JOURNEY_MISSING_INTEGRATION_ERROR

    This project is licensed under the MIT License - see the LICENSE file for details