---
title: Configure a React Native app for OATH MFA
description: Configure OATH-based MFA in a React Native app using @ping-identity/rn-oath to manage credentials and generate HOTP and TOTP one-time passcodes
component: orchsdks
page_id: orchsdks:journey:use-cases/oath/react-native/index
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/oath/react-native/index.html
llms_txt: https://developer.pingidentity.com/orchsdks/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
revdate: Wed, 03 Jun 2026 11:00:37 +0100
keywords: ["Journey", "Flows", "Tutorial", "Source Code", "Integration", "SDK", "React Native"]
section_ids:
  step_1_installing_the_module: Step 1. Installing the module
  step_2_creating_the_oath_client: Step 2. Creating the OATH client
  step_3_managing_credentials: Step 3. Managing credentials
  adding_oath_credentials_from_a_uri: Adding OATH credentials from a URI
  getting_oath_credentials: Getting OATH credentials
  updating_oath_credentials: Updating OATH credentials
  deleting_oath_credentials: Deleting OATH credentials
  step_4_generating_oath_based_one_time_passcodes: Step 4. Generating OATH-based one-time passcodes
  generating_hotp_codes: Generating HOTP codes
  generating_totp_codes: Generating TOTP codes
  step_5_handling_errors: Step 5. Handling errors
  error_codes: Error codes
  step_6_closing_the_oath_client: Step 6. Closing the OATH client
  health-check-policies: Enabling OATH device health check policies
  handling_device_health_check_policy_violations: Handling device health check policy violations
---

# Configure a React Native app for OATH MFA

[icon: circle-check, set=far]PingOne Advanced Identity Cloud [icon: circle-check, set=far]PingAM [icon: react, set=fab]React Native

This page guides you through configuring OATH-based multi-factor authentication (MFA) in a React Native application using `@ping-identity/rn-oath`.

## Step 1. Installing the module

To install the module into your React Native project, use `yarn` or `npm` as follows:

* yarn

* npm

```shell
yarn add @ping-identity/rn-oath
```

```shell
npm install @ping-identity/rn-oath
```

After installation, import the functions you might need:

Importing the OATH module

```typescript
import {
  createOathClient,
  configureOathPolicyEvaluator,
  OathError,
} from '@ping-identity/rn-oath';
import type {
  OathClient,
  OathClientConfig,
  OathCodeInfo,
  OathCredential,
  OathMfaPolicy,
} from '@ping-identity/rn-oath';
```

## Step 2. Creating the OATH client

To use the **OATH** module you must initialize the OATH client in your application by calling the `createOathClient()` method:

Creating the OATH client

```typescript
import { createOathClient } from '@ping-identity/rn-oath';

const oathClient = await createOathClient();
```

You can customize the OATH client with a number of options:

Creating a customized OATH client

```typescript
import { createOathClient } from '@ping-identity/rn-oath';
import { logger } from '@ping-identity/rn-logger';

const oathLogger = logger({ level: 'debug' });

const oathClient = await createOathClient({
  logger: oathLogger,
  timeout: 30,
  enableCredentialCache: false,
});
```

The properties you can use to customize OATH client configuration are as follows:

| Option                  | Required | Description                                                                                                                                                                                                                                                                               |
| ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `logger`                | No       | A `LoggerInstance` created by `logger({ level })` from `@ping-identity/rn-logger`.Records credential operations at `debug`/`info` level and errors at `error` level.Learn more in [Configuring logging in React Native](../../../customization/logging/react-native-custom-logging.html). |
| `timeout`               | No       | Network timeout, in seconds. Must be greater than or equal to `0`.Default is `15` seconds.                                                                                                                                                                                                |
| `enableCredentialCache` | No       | Whether to cache credentials in memory for faster repeated reads.Default is `false`.                                                                                                                                                                                                      |
| `encryptionEnabled`     | No       | (iOS only) Whether to encrypt the credential store in the Keychain.Default is `true`.Do not disable in a production app.                                                                                                                                                                  |
| `storage`               | No       | An `OathStorageHandle` for custom storage. Omit to use the platform default; iOS Keychain or Android KeyStore.Learn more in [Customizing Journey module storage in React Native](../../../customization/storage/customize-react-native-storage.html).                                     |
| `policyEvaluator`       | No       | An `OathPolicyEvaluatorHandle` returned by `configureOathPolicyEvaluator()`.Enables device health check policy enforcement when generating codes.Learn more in [Enabling OATH device health check policies](#health-check-policies).                                                      |

## Step 3. Managing credentials

The **OATH** module relies on a set of credentials that you can create, retrieve, update, and delete.

Each credential contains the service and account details, and the parameters required to generate HOTP or TOTP codes.

### Adding OATH credentials from a URI

The OATH module lets the user register their device for OATH-based multi-factor authentication (MFA).

The information required to register a device is contained in a specially-encoded URI, which your client application decodes to create the credentials.

This URI is often delivered by QR codes that the client can scan, or directly in the callback output by the [OATH Registration node](https://docs.pingidentity.com/auth-node-ref/latest/oath-registration.html).

Use the `addCredentialFromUri()` method to create OATH credentials and register an MFA device:

Registering an OATH credential from a URI

```typescript
const credential = await oathClient.addCredentialFromUri(
  'otpauth://totp/Example%3Auser%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30',
);

console.log(credential.id);         // opaque stable identifier
console.log(credential.issuer);     // "Example"
console.log(credential.accountName); // "user@example.com"
console.log(credential.type);       // "TOTP"
```

The `otpauth://` URI format is `otpauth://TYPE/LABEL?PARAMETERS`

TOTP example

```none
otpauth://totp/Issuer:accountName?secret=BASE32SECRET&issuer=Issuer&algorithm=SHA1&digits=6&period=30
```

HOTP example

```none
otpauth://hotp/Issuer:accountName?secret=BASE32SECRET&issuer=Issuer&algorithm=SHA1&digits=6&counter=0
```

**OTPAUTH parameter reference**

| Parameter   | Required  | Description                                                                                                             |
| ----------- | --------- | ----------------------------------------------------------------------------------------------------------------------- |
| `secret`    | Yes       | Base32-encoded shared secret. Stored in the platform secure enclave; never accessible from JavaScript after enrollment. |
| `issuer`    | Yes       | Service name displayed to the user, for example, `Example Corp`.                                                        |
| `algorithm` | No        | The HMAC algorithm used to compute the one-time passcode.One of `SHA1` (default), `SHA256`, or `SHA512`.                |
| `digits`    | No        | Code length: `6` (default) or `8`.                                                                                      |
| `period`    | TOTP only | Code validity window, in seconds. Defaults to `30`.                                                                     |
| `counter`   | HOTP only | Starting counter value. Defaults to `0`.                                                                                |

|   |                                                                                                                                                                                                                                       |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | `addCredentialFromUri()` throws `OathError` with code `OATH_DUPLICATE_CREDENTIAL` if a credential with the same issuer and account name already exists.Catch this case to prompt the user to replace or keep the existing credential. |

### Getting OATH credentials

Call `getCredentials()` to load all stored credentials, or `getCredential(id)` to load a single credential by its `id`:

* All credentials

* Single credential

```typescript
// All credentials
const credentials = await oathClient.getCredentials();
```

Retrieving a single OATH credential by ID

```typescript
// Single credential
const credential = await oathClient.getCredential('abc123');
if (credential === null) {
  console.log('Credential not found');
} else {
    console.log('id:', credential.id);
    console.log('deviceName:', credential.deviceName);
    console.log('uuid:', credential.uuid);
    console.log('createdDate:', credential.createdDate);
    console.log('lastAccessDate:', credential.lastAccessDate);
  }
```

### Updating OATH credentials

Use `saveCredential()` to persist changes to an existing credential. Retrieve the credential first, modify the desired fields, and save it back:

Updating an OATH credential

```typescript
const credential = await oathClient.getCredential('abc123');
if (credential) {
  const updated = await oathClient.saveCredential({
    ...credential,
    displayIssuer: 'My Company',
    displayAccountName: 'Work Account',
  });
}
```

Validation rules enforced by `saveCredential()`:

* `digits` must be `6` or `8`.

* `period` must be `> 0` for TOTP credentials.

* `counter` must be `>= 0` for HOTP credentials.

### Deleting OATH credentials

Call `deleteCredential(id)` to remove a credential and its stored secret permanently:

Deleting an OATH credential

```typescript
try {
  await oathClient.deleteCredential('abc123');
  console.log('Credential removed');
} catch (err) {
  if (err instanceof OathError) {
    if (err.code === 'OATH_CREDENTIAL_NOT_FOUND') {
      console.warn('Credential does not exist');
    } else {
      throw err;
    }
  }
}
```

## Step 4. Generating OATH-based one-time passcodes

When a journey reaches the [OATH Token Verifier node](https://docs.pingidentity.com/auth-node-ref/latest/oath-token-verifier.html) node, it returns a `NameCallback` expecting a one-time passcode.

Generate the code from the stored credential, populate the callback, then advance the journey:

### Generating HOTP codes

HOTP codes are counter-based, so they do not expire.

A typical HOTP flow presents the user with their registered credential and a button to generate a code.

When the user taps the button, call `generateCode()` and submit the result to the journey.

Each call to `generateCode()` permanently increments the counter stored on the device. Do not call `generateCode()` until the user has confirmed they want a code, and submit it to the journey as soon as it is generated, as follows:

Generating an HOTP code and submitting it to a journey

```typescript
import { useJourney, useJourneyForm } from '@ping-identity/rn-journey';

const [node, actions] = useJourney(journeyClient);
const form = useJourneyForm(node);

const code = await oathClient.generateCode(credential.id);
form.setValueByType('NameCallback', code);

if (form.canSubmit) {
  await actions.next(form.input);
}
```

### Generating TOTP codes

For TOTP credentials, call `generateCodeWithValidity()` to get the current code along with its validity window, then submit it to the journey:

Generating a TOTP code and submitting it to a journey

```typescript
import { useJourney, useJourneyForm } from '@ping-identity/rn-journey';

const [node, actions] = useJourney(journeyClient);
const form = useJourneyForm(node);

const info = await oathClient.generateCodeWithValidity(credential.id);
form.setValueByType('NameCallback', info.code);

if (form.canSubmit) {
  await actions.next(form.input);
}
```

In addition to `info.code`, `generateCodeWithValidity()` returns timing metadata you can use to drive a countdown display:

| Field           | Type     | Description                                                                                      |
| --------------- | -------- | ------------------------------------------------------------------------------------------------ |
| `code`          | `string` | The current one-time passcode.                                                                   |
| `timeRemaining` | `number` | Seconds until the code expires.                                                                  |
| `progress`      | `number` | Fractional progress through the validity window (0.0–1.0). `0.0` when fresh, `1.0` when expired. |
| `totalPeriod`   | `number` | Total validity window in seconds.                                                                |
| `counter`       | `number` | Current HOTP counter value. `-1` for TOTP credentials.                                           |

## Step 5. Handling errors

All OATH operations throw `OathError` when something goes wrong.

Check `err instanceof OathError` to confirm the error is OATH-related, then inspect `err.code` to handle specific failure cases:

* Credential operations

* Code generation

Handling errors from credential operations

```typescript
import { OathError } from '@ping-identity/rn-oath';

try {
  const credential = await oathClient.addCredentialFromUri(uri);
} catch (err) {
  if (err instanceof OathError) {
    switch (err.code) {
      case 'OATH_DUPLICATE_CREDENTIAL':
        // Prompt the user to replace or keep the existing credential
        break;
      case 'OATH_INVALID_URI':
        // The scanned QR code is not a valid otpauth:// URI
        break;
      default:
        throw err;
    }
  }
}
```

Handling errors from code generation

```typescript
import { OathError } from '@ping-identity/rn-oath';

try {
  const info = await oathClient.generateCodeWithValidity(credential.id);
} catch (err) {
  if (err instanceof OathError) {
    switch (err.code) {
      case 'OATH_CREDENTIAL_LOCKED':
        // The credential is locked; inform the user
        break;
      case 'OATH_POLICY_VIOLATION':
        // A device health check policy blocked code generation
        break;
      default:
        throw err;
    }
  }
}
```

### Error codes

| Error code                    | Platform | Description                                                                            |
| ----------------------------- | -------- | -------------------------------------------------------------------------------------- |
| `OATH_CLEANUP_FAILED`         | iOS      | Native cleanup failed internally. Not thrown from `close()` — iOS always resolves.     |
| `OATH_CODE_GENERATION_FAILED` | Both     | The native SDK could not generate a code for this credential.                          |
| `OATH_CREDENTIAL_LOCKED`      | Both     | The credential is locked by a device policy; code generation is not allowed.           |
| `OATH_CREDENTIAL_NOT_FOUND`   | Both     | No credential with the given ID exists in the native store.                            |
| `OATH_DUPLICATE_CREDENTIAL`   | Both     | A credential with the same ID already exists in the native store.                      |
| `OATH_INITIALIZATION_FAILED`  | Both     | The native OATH session could not be created during `createOathClient`.                |
| `OATH_INVALID_PARAMETER`      | Both     | A method argument has an invalid value.                                                |
| `OATH_INVALID_URI`            | Both     | The provided `otpauth://` URI could not be parsed.                                     |
| `OATH_MISSING_PARAMETER`      | iOS      | A required method argument was not provided.                                           |
| `OATH_POLICY_VIOLATION`       | Both     | The operation was blocked by a device health check policy.                             |
| `OATH_STATE_ERROR`            | Both     | A method was called after `close()`, or the client is in an unexpected internal state. |
| `OATH_STORAGE_ACCESS_DENIED`  | iOS      | The app does not have permission to access the native credential store.                |
| `OATH_STORAGE_CORRUPTED`      | iOS      | Stored credential data is corrupted and cannot be read.                                |
| `OATH_STORAGE_FAILURE`        | Both     | The native credential store encountered an unspecified I/O error.                      |
| `OATH_UNKNOWN_ERROR`          | Both     | An unexpected error occurred that does not map to a specific code.                     |

## Step 6. Closing the OATH client

Call `close()` when the OATH client is no longer needed. This releases the native session and its associated memory:

Closing the OATH client

```typescript
try {
    // ... work with credentials
  } finally {
    await oathClient.close();
  }
```

Close the OATH client when the component is dismissed, for example in a `useEffect` cleanup function.

|   |                                                                                                                                                                                                      |
| - | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Calling any method on a closed OATH client throws an `OathError` with code `OATH_STATE_ERROR`. Always check that the OATH client is still active before calling methods from asynchronous callbacks. |

## Enabling OATH device health check policies

The **OATH** module gates one-time passcode generation behind device health check policies.

When a policy is violated, calling `generateCode()` or `generateCodeWithValidity()` throws `OathError` with code `OATH_POLICY_VIOLATION` instead of returning a code.

To enable device health check policies, configure a policy evaluator before creating the OATH client:

Configuring a device health check policy evaluator

```typescript
import {
  configureOathPolicyEvaluator,
  createOathClient,
} from '@ping-identity/rn-oath';

const deviceHealthEvaluator = configureOathPolicyEvaluator({
  policies: [
    { kind: 'biometricAvailable' },
    { kind: 'deviceTampering' },
  ],
});

const oathClient = await createOathClient({
  policyEvaluator: deviceHealthEvaluator,
});
```

| Policy kind          | Description                                                                                                                                                                                                                         |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `biometricAvailable` | Code generation fails if the device has no enrolled biometrics.Enforces that the user has set up biometric authentication as a prerequisite for OATH codes.                                                                         |
| `deviceTampering`    | Code generation fails if the device's jailbreak or root score exceeds the threshold encoded in the credential's server-supplied `policies` field.The threshold is set server-side and is not a client-side configuration parameter. |

|   |                                                                                                                                                                                                     |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | `configureOathPolicyEvaluator()` is synchronous and must be called before `createOathClient()`.Pass the returned `OathPolicyEvaluatorHandle` as `OathClientConfig.policyEvaluator`, as shown above. |

### Handling device health check policy violations

When a policy blocks code generation, catch `OATH_POLICY_VIOLATION` and display a contextual message:

Handling a device health check policy violation

```typescript
import { OathError } from '@ping-identity/rn-oath';

try {
  const code = await oathClient.generateCode(credential.id);
} catch (err) {
  if (err instanceof OathError) {
    if (err.code === 'OATH_POLICY_VIOLATION') {
      // Inform the user why the code cannot be generated
      setErrorMessage('This code cannot be generated on this device. Enroll biometrics or use an unmodified device.');
    } else {
      setErrorMessage(err.message);
    }
  }
}
```
