Skip to content

feat(mfa): Implement Multi-Factor Authentication (MFA) support#1517

Open
subhankarmaiti wants to merge 29 commits into
masterfrom
feat/flexible-factors-grant-support
Open

feat(mfa): Implement Multi-Factor Authentication (MFA) support#1517
subhankarmaiti wants to merge 29 commits into
masterfrom
feat/flexible-factors-grant-support

Conversation

@subhankarmaiti

@subhankarmaiti subhankarmaiti commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Adds first-class MFA support to the SDK across iOS, Android, and Web, exposing the Flexible Factors flow through a single consistent API:

  • mfa.getAuthenticators() — list a user's enrolled factors
  • mfa.enroll() — enroll a new factor (OTP, SMS, voice, email, push)
  • mfa.challenge() — trigger a challenge for an existing factor
  • mfa.verify() — verify a code and exchange the MFA token for credentials

An MfaFactorType enum and a dedicated MfaError (with MfaErrorCodes) are exported publicly for type-safe usage and error handling.

Usage

import { useAuth0, MfaFactorType } from 'react-native-auth0';                                                       
                                                  
const { mfa } = useAuth0();

// A login attempt against an MFA-enabled connection returns an mfa_token:
//   const mfaToken = error.json.mfa_token;                                                                                                  
 
// List the user's enrolled factors                                                                                                          
const authenticators = await mfa.getAuthenticators({ mfaToken });                                                   
                                                                                                                                             
// Verify an existing factor (e.g. TOTP from an authenticator app)
const credentials = await mfa.verify({ mfaToken, otp: '123456' });                                                                           
                                                                                                                    
// ...or enroll a new factor                      
const challenge = await mfa.enroll({                                                                                                         
  mfaToken,
  factorType: MfaFactorType.SMS,                                                                                                             
  phoneNumber: '+12025550135',                                                                                      
});                                                                                                                                          
 
if (challenge.type === 'oob') {                                                                                                              
  // user receives the code out-of-band, then:                                                                      
  const credentials = await mfa.verify({ mfaToken, oobCode: '000000' });
}

dependabot Bot and others added 2 commits April 23, 2026 10:18
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@subhankarmaiti subhankarmaiti requested a review from a team as a code owner April 23, 2026 04:56
Comment thread android/src/main/java/com/auth0/react/A0Auth0Module.kt Outdated
Comment thread android/src/main/java/com/auth0/react/A0Auth0Module.kt
Comment thread android/src/main/java/com/auth0/react/A0Auth0Module.kt
Comment thread android/src/main/java/com/auth0/react/MfaBridge.kt Outdated
)
}

fun enroll(mfaToken: String, type: String, value: String?, promise: Promise) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define type as enum so that its always limited to the supported factor

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the bridge boundary, the param has to stay String. React Native TurboModule codegen marshals primitives across the JS↔native boundary.
For consumers, the real type-safety lives in the TS layer, which exposes a public MfaFactorType

Comment thread example/android/app/build.gradle Outdated
Comment thread ios/A0MfaBridge.swift Outdated
Comment thread ios/A0MfaClient.swift
}
}

func enroll(mfaToken: String, type: String, value: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make type as an enum instead of String

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consumer side it already exposing the enum MfaFactorType. which throw compile time error if passed any other strings.

@subhankarmaiti subhankarmaiti force-pushed the feat/flexible-factors-grant-support branch from 71772d0 to 714794d Compare April 29, 2026 12:12
@auth0 auth0 deleted a comment from pmathew92 Apr 29, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 23b07c21-a5dd-44c0-9698-153ea4d0c698

📥 Commits

Reviewing files that changed from the base of the PR and between 900f7a3 and e8a1a09.

📒 Files selected for processing (1)
  • EXAMPLES.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • EXAMPLES.md

📝 Walkthrough

Walkthrough

Adds MFA Flexible Factors Grant support across shared types, native and web bridge layers, React client wiring, example MFA flows, and docs. Also threads credentialsManagerStorageKey through native initialization paths.

Changes

MFA Flexible Factors Grant and per-client credential storage

Layer / File(s) Summary
MFA types, parameters, and errors
src/types/common.ts, src/types/parameters.ts, src/core/models/MfaError.ts, src/core/models/index.ts, src/core/models/__tests__/MfaError.spec.ts, src/core/models/__tests__/ErrorCodes.spec.ts
Defines MFA authenticator, challenge, enrollment, factor, and request parameter types; adds MfaErrorCodes and MfaError; and updates model exports and tests.
MFA client interface and public exports
src/core/interfaces/IMfaClient.ts, src/core/interfaces/index.ts, src/core/interfaces/IAuth0Client.ts, src/Auth0.ts, src/index.ts
Adds IMfaClient, extends IAuth0Client and Auth0ContextInterface with mfa, adds Auth0.mfa, and re-exports MFA types and interfaces publicly.
Native bridge contracts
src/platforms/native/bridge/INativeBridge.ts, src/platforms/native/bridge/NativeBridgeManager.ts, src/specs/NativeA0Auth0.ts
Extends native initialization with credentialsManagerStorageKey and adds MFA bridge methods to the native interface, manager, and codegen spec.
Android and iOS native bridge implementation
android/src/main/java/com/auth0/react/MfaClient.kt, android/src/main/java/com/auth0/react/A0Auth0Module.kt, android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt, ios/A0MfaClient.swift, ios/NativeBridge.swift, ios/A0Auth0.mm
Implements Android and iOS MFA bridge handling, adds storage-key-aware credential manager setup, and exports the MFA React Native methods.
Native and web adapters
src/platforms/native/adapters/NativeMfaClient.ts, src/platforms/native/adapters/NativeAuth0Client.ts, src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts, src/platforms/web/adapters/WebMfaClient.ts, src/platforms/web/adapters/WebAuth0Client.ts, src/platforms/web/adapters/__tests__/WebMfaClient.spec.ts, src/platforms/native/adapters/index.ts, src/platforms/web/adapters/index.ts
Adds native and web MFA client adapters, wires them into platform clients, and updates adapter tests and exports.
React provider/context wiring
src/hooks/Auth0Provider.tsx, src/hooks/Auth0Context.ts
Adds mfa to the provider context, recreates the Auth0 client on config signature changes, and handles MFA verify completion by saving credentials and dispatching login completion.
Example app MFA wizard UI
example/src/App.web.tsx, example/src/screens/hooks/Home.tsx, example/src/screens/class-based/ClassLogin.tsx, example/src/screens/class-based/ClassApiTests.tsx
Adds multi-step MFA flows to the example app screens, including token capture, authenticator listing, enrollment, challenge, verification, and completion views.
Documentation, changelog, and release workflow
EXAMPLES.md, EXAMPLES-WEB.md, README.md, CHANGELOG.md, .github/workflows/release.yml
Adds MFA docs and examples, extends the platform support matrix, records v5.7.0, and changes release workflow quoting.

Sequence Diagram(s)

sequenceDiagram
  participant App as App code
  participant Provider as Auth0Provider
  participant Client as NativeAuth0Client or WebAuth0Client
  participant Mfa as Platform MFA adapter
  participant Bridge as Native bridge
  participant SDK as Auth0 SDK

  App->>Provider: mfa.getAuthenticators(mfaToken)
  Provider->>Client: getAuthenticators(...)
  Client->>Mfa: getAuthenticators(...)
  Mfa->>Bridge: getMfaAuthenticators(...) / spaMfa.getAuthenticators(...)
  Bridge->>SDK: platform MFA call
  SDK-->>Bridge: authenticators
  Bridge-->>Provider: result

  App->>Provider: mfa.verify(...)
  Provider->>Client: verify(...)
  Client->>Mfa: verify(...)
  Mfa->>Bridge: mfaVerify(...)
  Bridge->>SDK: verify credentials
  SDK-->>Provider: credentials
  Provider->>Provider: saveCredentials + LOGIN_COMPLETE
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • auth0/react-native-auth0#1564: Threads credentialsManagerStorageKey through native initialization and credential storage, which overlaps with this PR’s native storage-key plumbing.

Suggested reviewers

  • pmathew92

🐇 Hop-hop, the MFA trail is bright,
Six little factors dance in sight.
Tokens, challenges, codes, and keys,
Crossed from native to web with ease.
I found the path, then tucked it right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding MFA support across the library and examples.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/flexible-factors-grant-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@subhankarmaiti subhankarmaiti changed the base branch from master to chore/upgrade-react-native-0.86 June 24, 2026 06:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt (1)

204-209: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore the bridge annotations for passkeyEnrollmentChallenge.

Without @ReactMethod/@DoNotStrip, this method won’t be exported on Android old architecture, while the JS spec and bridge still expose passkeyEnrollmentChallenge.

Proposed fix
+    `@ReactMethod`
+    `@DoNotStrip`
     abstract fun passkeyEnrollmentChallenge(
         accessToken: String,
         userIdentity: String?,
         connection: String?,
         promise: Promise
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt` around lines 204 -
209, The Android old-architecture bridge method passkeyEnrollmentChallenge in
A0Auth0Spec is missing the annotations needed for export. Add back the React
Native bridge annotations on this abstract method so it remains discoverable
from JS, using the same pattern as the other bridged methods in A0Auth0Spec,
specifically `@ReactMethod` and `@DoNotStrip`.
🧹 Nitpick comments (5)
src/types/parameters.ts (1)

286-288: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider typing factorsAllowed as MfaFactorType[].

factorsAllowed?: string[] loses the discriminant typing already used elsewhere in this file. If the values are intended to be factor types, a MfaFactorType[] (or the literal union) gives callers autocomplete and prevents typos.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/parameters.ts` around lines 286 - 288, The
MfaGetAuthenticatorsParameters type is using a loose string[] for
factorsAllowed, which drops the factor discriminant typing used elsewhere.
Update factorsAllowed in MfaGetAuthenticatorsParameters to use MfaFactorType[]
(or the existing literal union type if that is the canonical form) so callers
get autocomplete and type safety, and make sure any related references in this
file stay consistent with the MFA factor type definitions.
src/core/models/MfaError.ts (1)

70-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Strengthen typing of the error-code map and type.

ERROR_CODE_MAP: Record<string, string> and type: string discard the literal information already encoded in MfaErrorCodes. Typing the values as (typeof MfaErrorCodes)[keyof typeof MfaErrorCodes] lets e.type === MfaErrorCodes.INVALID_OTP comparisons stay type-safe for consumers.

♻️ Suggested typing
+type MfaErrorCode = (typeof MfaErrorCodes)[keyof typeof MfaErrorCodes];
+
-const ERROR_CODE_MAP: Record<string, string> = {
+const ERROR_CODE_MAP: Record<string, MfaErrorCode> = {
-  public readonly type: string;
+  public readonly type: MfaErrorCode;

As per coding guidelines: "Avoid using any types; use strict TypeScript typing instead".

Also applies to: 141-151

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/models/MfaError.ts` at line 70, Strengthen the typing in MfaError by
replacing the broad string-based declarations for ERROR_CODE_MAP and the error
type with the literal union derived from MfaErrorCodes, so consumers keep
type-safe comparisons like e.type === MfaErrorCodes.INVALID_OTP. Update the
MfaError class and any related constructor/field typings in the MfaError
implementation to use the inferred value type from MfaErrorCodes instead of
string, and make sure the map preserves the existing enum-like literals
throughout the code paths referenced by MfaErrorCodes and MfaError.

Source: Coding guidelines

src/platforms/native/adapters/NativeMfaClient.ts (1)

21-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the exported MFA adapter.

NativeMfaClient is part of the public native adapter surface now, but the class and its public methods were added without JSDoc. Please add API docs here so the new MFA bridge contract is discoverable and consistent with the rest of src/**/*.ts. As per coding guidelines, src/**/*.ts: Use JSDoc comments to document all public APIs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platforms/native/adapters/NativeMfaClient.ts` around lines 21 - 108, The
exported NativeMfaClient adapter is public but its API is undocumented; add
JSDoc to the class and each public method (constructor, getAuthenticators,
enroll, challenge, and verify) so the MFA bridge contract is discoverable and
matches the documentation style used across src/**/*.ts. Include brief
descriptions of each method’s purpose, parameters, and return values, and keep
the docs aligned with the existing IMfaClient/INativeBridge terminology to make
the adapter surface consistent.

Source: Coding guidelines

src/platforms/web/adapters/WebMfaClient.ts (2)

44-46: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

factorsAllowed filter is silently dropped.

MfaGetAuthenticatorsParameters accepts an optional factorsAllowed, but only parameters.mfaToken is forwarded to spaMfa.getAuthenticators. Callers passing a filter will receive an unfiltered list. Either apply the filter client-side or document that the web platform ignores it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platforms/web/adapters/WebMfaClient.ts` around lines 44 - 46, The
WebMfaClient.getAuthenticators path is dropping the optional factorsAllowed
filter because only parameters.mfaToken is passed into spaMfa.getAuthenticators.
Update WebMfaClient so it either forwards or applies parameters.factorsAllowed
before returning authenticators, using the existing
MfaGetAuthenticatorsParameters and getAuthenticators symbols to locate the
change. If filtering cannot be supported on web, make that behavior explicit
instead of silently ignoring it.

73-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid any for the spa request payloads.

spaParams is typed any, which suppresses type checking against the spa-js request types. Prefer the concrete spa-js parameter type (or a discriminated local type) so payload mismatches are caught at compile time.

As per coding guidelines: "Avoid using any types; use strict TypeScript typing instead."

Also applies to: 152-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platforms/web/adapters/WebMfaClient.ts` around lines 73 - 76, Avoid using
any for the spa request payloads in WebMfaClient by replacing spaParams with the
appropriate spa-js parameter type or a strict local discriminated union so
TypeScript can validate the request shape. Update the mfaToken/factorType
payload construction in the affected WebMfaClient methods (including the other
matching spaParams assignment) to use concrete typing and keep the payload
compatible with the spa-js request APIs.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/src/main/java/com/auth0/react/MfaClient.kt`:
- Around line 58-68: The factor normalization in mapFactorType() is incorrectly
collapsing voice into sms, which causes voice-only options to be exposed as SMS
authenticators. Update mapFactorType() in MfaClient to preserve voice as its own
factor instead of mapping it to sms, while keeping the existing mappings for
otp, sms, email, push, oob, and recovery-code unchanged.

In `@example/src/App.web.tsx`:
- Around line 453-456: The QR image generation in the App.web.tsx TOTP
enrollment flow is leaking the secret by sending enrollmentChallenge.barcodeUri
to a third-party service. Update the Image usage in the enrollment UI to avoid
remote QR generation: use a local/in-app QR renderer or remove the QR image
entirely and present only the deep link/secret, and apply the same change
anywhere else the same pattern appears in the enrollment screen.

In `@example/src/screens/class-based/ClassApiTests.tsx`:
- Around line 182-185: The MFA verification flow in ClassApiTests and the
runTest helper is exposing raw credential-bearing results from
auth0.mfa().verify(), which can leak sensitive tokens into logs and UI. Update
the Verify OTP path to sanitize the returned value before passing it to runTest,
and ensure runTest only logs/displays a redacted summary rather than the full
verification response. Use the auth0.mfa().verify call site and runTest as the
key places to apply the redaction.
- Line 153: The MFA enrollment call in ClassApiTests is using the wrong property
name and the verification flow is leaking returned credentials through runTest.
Update the auth0.mfa().enroll invocation to use factorType instead of type, and
adjust the nearby mfa().verify path so it is not wrapped in runTest when it
returns credentials; keep the tokens out of console/UI exposure by handling that
result internally in the test helper or directly awaiting it.

In `@example/src/screens/class-based/ClassLogin.tsx`:
- Around line 418-421: The QR image generation in ClassLogin’s Image source is
leaking the TOTP secret by sending enrollmentChallenge.barcodeUri to a
third-party service. Replace the api.qrserver.com URL usage with an in-app/local
QR renderer, or remove the QR image entirely and present only the deep link or
secret locally. Keep the fix within the ClassLogin component where the
enrollment QR is rendered so the secret never leaves the device.
- Around line 259-272: After mfaClient.verify succeeds in ClassLogin, the flow
only updates local state and never completes authentication. Update the MFA
success branch to persist the returned credentials and trigger the same
post-login navigation used by the password-login success path, rather than only
calling setResult and setMfaStep('complete'). Use the existing login success
handling in ClassLogin as the reference so the user is fully authenticated and
redirected after MFA.

In `@example/src/screens/hooks/Home.tsx`:
- Around line 482-485: The QR image generation in Home uses the third-party
api.qrserver.com URL with enrollmentChallenge.barcodeUri, which leaks the TOTP
secret off-device. Update the Image rendering path to avoid sending barcodeUri
to an external service; instead use an in-app/local QR renderer, or remove the
QR image and present only the deep link/secret in this screen. Keep the fix
localized around the enrollmentChallenge.barcodeUri usage in Home.tsx.

In `@EXAMPLES-WEB.md`:
- Around line 275-276: The web direct-auth note conflicts with the support
matrix for auth.passwordRealm(), so update the documentation to state one
consistent behavior across both places. Use the relevant auth method references
in EXAMPLES-WEB.md, especially auth.passwordRealm(), auth.createUser(),
auth.resetPassword(), and MFA Flexible Factors Grant, and either remove the web
support claim or adjust the matrix so both docs match the same
supported/unsupported status.
- Around line 179-205: The MFA example in the documentation is logging sensitive
credentials, so update the example flow in the mfa enrollment and verification
snippets to stop printing the TOTP secret and access token to the console. Keep
the surrounding MfaError handling and the enrollTotp/verifyOtp examples intact,
but replace the console output in the challenge.type === 'totp' and successful
verify paths with non-sensitive guidance or remove those logs entirely.

In `@EXAMPLES.md`:
- Around line 1818-1820: The sample MFA logging in EXAMPLES.md is printing
sensitive values, so update the affected example blocks to avoid outputting
secrets, codes, or tokens. In the challenge-handling examples (including the
code paths that reference challenge.secret, oobCode, and accessToken), replace
the current console.log statements with redacted or generic status messages that
confirm progress without exposing credentials. Use the relevant example sections
around the MFA challenge flows to locate and update all affected snippets.

In `@ios/A0MfaClient.swift`:
- Around line 77-95: The `.voice` case in `A0MfaClient.enroll` is currently
sharing the same `enroll(mfaToken:phoneNumber:)` path as `.phone`, which drops
the channel distinction for iOS. Update the `switch factorType` handling so
`voice` is either routed through a voice-specific native enrollment path in
`mfaClient` or explicitly rejected as unsupported, while keeping `.phone` on the
SMS/phone-number flow. Use the existing `factorType` switch and
`mfaClient.enroll` call site to locate the change.

In `@src/core/models/__tests__/ErrorCodes.spec.ts`:
- Around line 257-265: The overlap checks in ErrorCodes.spec use `includes(code
as any)`, which hides a type mismatch with the literal arrays. Update the test
data types so `webAuthArray`, `credentialsArray`, and `dpopArray` are typed
compatibly (for example as `string[]` or a shared string union) and then call
`includes` without `as any` in the `mfaArray.filter` checks. Keep the fix local
to the overlap test block and preserve the existing `webAuthOverlaps`,
`credentialsOverlaps`, and `dpopOverlaps` assertions.

In `@src/core/models/MfaError.ts`:
- Around line 70-99: The `ERROR_CODE_MAP` in `MfaError` is mapping the generic
`invalid_grant` response directly to `MfaErrorCodes.INVALID_OTP`, which can
misclassify expired or invalid MFA tokens. Update the MFA error parsing logic in
`MfaError` so `invalid_grant` is not blindly normalized in the same way for all
flows; inspect the accompanying `error_description` or parsed JSON/error payload
first, then map token-expiry or token-invalid cases to `EXPIRED_MFA_TOKEN` or
`INVALID_MFA_TOKEN`, and only fall back to `INVALID_OTP` when the response
clearly indicates an OTP failure.

In `@src/factory/Auth0ClientFactory.web.ts`:
- Line 26: The web SPA client singleton in WebAuth0Client.getSpaClient() is
still shared across all configs, so a new Auth0ClientFactory.web.ts signature
can still end up reusing the first Auth0Client. Update the singleton handling in
WebAuth0Client and its getSpaClient() path so it is keyed by the same config
signature used by getConfigSignature(options), or reset/recreate the cached
spa-js client whenever the signature changes. Make sure the cache invalidation
logic is tied to the existing Auth0ClientFactory.web.ts singleton flow so
tenant/config switching returns the correct Auth0Client.

In `@src/hooks/Auth0Provider.tsx`:
- Around line 52-58: Reset the auth state when the tenant signature changes in
Auth0Provider so the old client’s user/error do not persist while the new Auth0
instance initializes. Update the state management around the reducer and the
initialization effect tied to configSignature to clear the current user and
error and set loading true before starting the new client setup. Use the
Auth0Provider, getConfigSignature, and the async initialization effect that
follows the useMemo client creation as the places to apply the reset.

In `@src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts`:
- Around line 5-27: The `mockBridge` declaration in `NativeMfaClient.spec` is
too strongly typed for the subset of `INativeBridge` methods it actually
provides, which breaks strict TypeScript compilation. Update the test mock to
use a partial/selected type instead of `jest.Mocked<INativeBridge>`, keeping
only the methods exercised by the MFA tests (and any already-used helpers) while
preserving proper Jest mocks. Use the `INativeBridge` interface and the
`mockBridge` constant as the key references when adjusting the type.

In `@src/platforms/native/adapters/NativeAuth0Client.ts`:
- Around line 141-145: The native config refresh in NativeAuth0Client is only
protected by an instance-level syncLock, so sibling clients can still interleave
initialize() and the subsequent bridge call and run against the wrong tenant
config. Update the shared native access path in NativeAuth0Client so the re-sync
and the original bridge invocation are serialized with a process-wide guard, and
make sure the direct getDPoPHeaders flow uses the same protection instead of
bypassing syncNativeConfig().

---

Outside diff comments:
In `@android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt`:
- Around line 204-209: The Android old-architecture bridge method
passkeyEnrollmentChallenge in A0Auth0Spec is missing the annotations needed for
export. Add back the React Native bridge annotations on this abstract method so
it remains discoverable from JS, using the same pattern as the other bridged
methods in A0Auth0Spec, specifically `@ReactMethod` and `@DoNotStrip`.

---

Nitpick comments:
In `@src/core/models/MfaError.ts`:
- Line 70: Strengthen the typing in MfaError by replacing the broad string-based
declarations for ERROR_CODE_MAP and the error type with the literal union
derived from MfaErrorCodes, so consumers keep type-safe comparisons like e.type
=== MfaErrorCodes.INVALID_OTP. Update the MfaError class and any related
constructor/field typings in the MfaError implementation to use the inferred
value type from MfaErrorCodes instead of string, and make sure the map preserves
the existing enum-like literals throughout the code paths referenced by
MfaErrorCodes and MfaError.

In `@src/platforms/native/adapters/NativeMfaClient.ts`:
- Around line 21-108: The exported NativeMfaClient adapter is public but its API
is undocumented; add JSDoc to the class and each public method (constructor,
getAuthenticators, enroll, challenge, and verify) so the MFA bridge contract is
discoverable and matches the documentation style used across src/**/*.ts.
Include brief descriptions of each method’s purpose, parameters, and return
values, and keep the docs aligned with the existing IMfaClient/INativeBridge
terminology to make the adapter surface consistent.

In `@src/platforms/web/adapters/WebMfaClient.ts`:
- Around line 44-46: The WebMfaClient.getAuthenticators path is dropping the
optional factorsAllowed filter because only parameters.mfaToken is passed into
spaMfa.getAuthenticators. Update WebMfaClient so it either forwards or applies
parameters.factorsAllowed before returning authenticators, using the existing
MfaGetAuthenticatorsParameters and getAuthenticators symbols to locate the
change. If filtering cannot be supported on web, make that behavior explicit
instead of silently ignoring it.
- Around line 73-76: Avoid using any for the spa request payloads in
WebMfaClient by replacing spaParams with the appropriate spa-js parameter type
or a strict local discriminated union so TypeScript can validate the request
shape. Update the mfaToken/factorType payload construction in the affected
WebMfaClient methods (including the other matching spaParams assignment) to use
concrete typing and keep the payload compatible with the spa-js request APIs.

In `@src/types/parameters.ts`:
- Around line 286-288: The MfaGetAuthenticatorsParameters type is using a loose
string[] for factorsAllowed, which drops the factor discriminant typing used
elsewhere. Update factorsAllowed in MfaGetAuthenticatorsParameters to use
MfaFactorType[] (or the existing literal union type if that is the canonical
form) so callers get autocomplete and type safety, and make sure any related
references in this file stay consistent with the MFA factor type definitions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e1e08998-d3c8-4f45-bcbf-c3e1fd415f0e

📥 Commits

Reviewing files that changed from the base of the PR and between 90e6c45 and 442d1af.

⛔ Files ignored due to path filters (4)
  • example/Gemfile.lock is excluded by !**/*.lock
  • example/ios/Podfile.lock is excluded by !**/*.lock
  • src/plugin/__tests__/__snapshots__/withAuth0-test.ts.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (53)
  • .github/workflows/release.yml
  • A0Auth0.podspec
  • CHANGELOG.md
  • EXAMPLES-WEB.md
  • EXAMPLES.md
  • README.md
  • android/src/main/java/com/auth0/react/A0Auth0Module.kt
  • android/src/main/java/com/auth0/react/MfaClient.kt
  • android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt
  • example/Gemfile
  • example/android/gradle/wrapper/gradle-wrapper.properties
  • example/android/gradlew
  • example/android/gradlew.bat
  • example/ios/Auth0Example.xcodeproj/project.pbxproj
  • example/package.json
  • example/src/App.web.tsx
  • example/src/screens/class-based/ClassApiTests.tsx
  • example/src/screens/class-based/ClassLogin.tsx
  • example/src/screens/hooks/Home.tsx
  • ios/A0Auth0.mm
  • ios/A0MfaClient.swift
  • ios/NativeBridge.swift
  • package.json
  • src/Auth0.ts
  • src/core/interfaces/IAuth0Client.ts
  • src/core/interfaces/IMfaClient.ts
  • src/core/interfaces/index.ts
  • src/core/models/MfaError.ts
  • src/core/models/__tests__/ErrorCodes.spec.ts
  • src/core/models/__tests__/MfaError.spec.ts
  • src/core/models/index.ts
  • src/core/utils/__tests__/configSignature.spec.ts
  • src/core/utils/configSignature.ts
  • src/core/utils/index.ts
  • src/factory/Auth0ClientFactory.ts
  • src/factory/Auth0ClientFactory.web.ts
  • src/factory/__tests__/Auth0ClientFactory.spec.ts
  • src/hooks/Auth0Context.ts
  • src/hooks/Auth0Provider.tsx
  • src/index.ts
  • src/platforms/native/adapters/NativeAuth0Client.ts
  • src/platforms/native/adapters/NativeMfaClient.ts
  • src/platforms/native/adapters/__tests__/NativeAuth0Client.spec.ts
  • src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts
  • src/platforms/native/adapters/index.ts
  • src/platforms/native/bridge/INativeBridge.ts
  • src/platforms/native/bridge/NativeBridgeManager.ts
  • src/platforms/web/adapters/WebAuth0Client.ts
  • src/platforms/web/adapters/WebMfaClient.ts
  • src/platforms/web/adapters/index.ts
  • src/specs/NativeA0Auth0.ts
  • src/types/common.ts
  • src/types/parameters.ts
💤 Files with no reviewable changes (1)
  • example/android/gradlew

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (17)
android/src/main/java/com/auth0/react/MfaClient.kt (1)

58-68: 🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Auth0 Android SDK 3.19.0 MfaEnrollmentType voice MFA enrollment supported value

💡 Result:

In the Auth0 Android SDK (including version 3.19.0), the MfaEnrollmentType class is used to specify the type of MFA factor for enrollment [1][2]. While the SDK supports phone-based MFA, it does not have a distinct MfaEnrollmentType value specifically named "voice" [1][2]. Instead, you use MfaEnrollmentType.Phone(phoneNumber: String) to initiate enrollment for phone-based factors [1][2]. The Auth0 API handles SMS and voice as channels for this factor [3][4]. When enrolling or challenging via the API, the channel is typically distinguished by the oob_channels parameter, where you specify "sms" or "voice" [3]. In the context of the Android SDK's MfaClient, the Phone type is designed to collect the phone number [1][2]. If you need to specify a preference for voice, you may need to ensure your backend or Auth0 tenant configuration is set to support voice as an OOB (Out-of-Band) channel, as the mobile SDK interfaces generally abstract these as phone enrollments [3][4]. Top results: [1] https://github.com/auth0/Auth0.Android/blob/main/EXAMPLES.md [3] https://auth0.com/docs/secure/multi-factor-authentication/authenticate-using-ropg-flow-with-mfa/enroll-challenge-sms-voice-authenticators [2] https://github.com/auth0/Auth0.android/blob/main/EXAMPLES.md [4] https://auth0.com/docs/secure/multi-factor-authentication/manage-mfa-auth0-apis/manage-authenticator-factors-mfa-api

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and read the relevant sections with line numbers.
wc -l android/src/main/java/com/auth0/react/MfaClient.kt
sed -n '1,220p' android/src/main/java/com/auth0/react/MfaClient.kt | cat -n

# Find related MFA factor/type usage in the Android bridge.
rg -n "MfaFactorType|MfaEnrollmentType|voice|push-notification|recovery-code|mapFactorType|mapEnrollment" android/src/main/java -S

Repository: auth0/react-native-auth0

Length of output: 13148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the Auth0 Android SDK API references in the repo, if vendored or documented here.
rg -n "class MfaEnrollmentType|object MfaEnrollmentType|sealed class MfaEnrollmentType|enum class MfaFactorType|voice" -S .

Repository: auth0/react-native-auth0

Length of output: 2387


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect any nearby tests or docs that describe MFA factor mapping behavior.
rg -n "mapFactorType|mapEnrollment|phone|sms|voice|MfaClient" android -S

Repository: auth0/react-native-auth0

Length of output: 2899


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' android/src/main/java/com/auth0/react/MfaClient.kt | cat -n

Repository: auth0/react-native-auth0

Length of output: 10589


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect JS-facing MFA parameter docs and native adapters for voice handling.
sed -n '300,360p' src/types/parameters.ts | cat -n
sed -n '40,95p' src/platforms/native/adapters/NativeMfaClient.ts | cat -n
sed -n '75,95p' src/platforms/web/adapters/WebMfaClient.ts | cat -n
sed -n '70,95p' ios/A0MfaClient.swift | cat -n

Repository: auth0/react-native-auth0

Length of output: 6179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the nearby README/Examples snippets that describe voice MFA enrollment/challenge.
rg -n -A8 -B4 "voice" README.md EXAMPLES.md src/types/parameters.ts src/platforms/native/adapters/NativeMfaClient.ts ios/A0MfaClient.swift

Repository: auth0/react-native-auth0

Length of output: 9334


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the iOS voice factor mapping and any Android factor-list/filter usage for comparison.
sed -n '1,140p' ios/A0MfaClient.swift | cat -n
rg -n "getAuthenticators|mapFactorType|MfaFactorType|voice" android/src/main/java/com/auth0/react -S

Repository: auth0/react-native-auth0

Length of output: 8709


🌐 Web query:

Auth0 Android MFA getAuthenticators voice sms factor type oob_channels phone voice

💡 Result:

In the Auth0 ecosystem, including for Android, SMS and Voice are categorized as Out-of-Band (OOB) authentication factors [1][2]. When interacting with the Auth0 MFA API (such as when using the Resource Owner Password Grant), these factors are handled as follows: 1. Factor Type: The authenticator_type is identified as oob [1][3]. 2. OOB Channels: The specific delivery method is distinguished by the oob_channel parameter, which accepts values of either sms or voice [3][4][2]. When enrolling a user for phone-based MFA via the API, you must specify both the type and the channel [3][2]. For example, when calling the /mfa/associate endpoint, you would provide: - authenticator_types: ["oob"] [4][2] - oob_channels: ["sms"] or ["voice"] [4][2] Auth0 treats SMS and Voice as distinct authenticators for the same phone number [1]. If both are enabled for a user, enrolling in one may automatically create authenticators for both, and deleting one may remove the other [1]. In the Auth0.Android SDK, you should utilize the MfaApiClient class for managing these interactions, as legacy authentication methods are deprecated [5][6]. The SDK requires that the MFA grant type is enabled in your application's Advanced Settings in the Auth0 Dashboard [7][8]. Note that while the SDK facilitates these flows, your application must provide the user interface to collect the phone number, prompt for the binding code (the OTP sent via SMS/Voice), and handle the authentication challenge [3][7][9].

Citations:


🌐 Web query:

Auth0 MFA factors allowed voice sms phone getAuthenticators factor type oob_channels

💡 Result:

In Auth0's Multi-Factor Authentication (MFA) system, SMS and voice are supported factors [1][2]. When interacting with the Auth0 Authentication API, specifically the MFA endpoints (such as getAuthenticators), these phone-based factors are categorized under the oob (Out-of-Band) authenticator type [3][4][5]. When a user is enrolled in SMS or voice, their authenticator record in the API response includes an oob_channel field that specifies the delivery method, which can be sms or voice [3][4][5]. Key details regarding these factors include: - OOB Channels: The oob_channels parameter, used during enrollment or identification in the Authentication API, accepts the values sms or voice to determine how the MFA code is delivered [4][5]. - Automatic Enrollment: If both SMS and voice are enabled for a user, enrolling a phone number typically results in Auth0 creating two separate authenticator entries for that number—one for SMS and one for voice [3]. - Deletion Behavior: Because these entries are linked to the same phone number, deleting either the SMS or voice authenticator will automatically delete the other [3]. - API Usage: To list enrolled factors, you use the GET /mfa/authenticators endpoint [3][6]. When enrolling or challenging these factors, you must specify the authentication_types as [oob] and the oob_channels as either [sms] or [voice] [4][5]. For comprehensive management, Auth0 documentation differentiates between independent factors (such as Phone Message, which covers SMS/Voice) and dependent factors [7]. SMS and voice configurations can be managed through the Auth0 Dashboard or the Management API's /api/v2/guardian/factors/phone/message-types endpoint, which supports setting message_types to ["sms"], ["voice"], or both [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' src/platforms/native/adapters/NativeMfaClient.ts | cat -n

Repository: auth0/react-native-auth0

Length of output: 3884


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "getAuthenticators|factorsAllowed|factorType === 'sms'|factorType === 'voice'|voice" src/platforms/native/adapters/NativeMfaClient.ts src/platforms/web/adapters/WebMfaClient.ts src/types/parameters.ts -S

Repository: auth0/react-native-auth0

Length of output: 1210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the JS type definitions and bridge contract for MFA authenticators/factors.
sed -n '260,310p' src/types/parameters.ts | cat -n
sed -n '1,140p' src/platforms/native/bridge.ts | cat -n
sed -n '1,120p' src/platforms/web/adapters/WebMfaClient.ts | cat -n

Repository: auth0/react-native-auth0

Length of output: 1883


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A12 -B6 "getAuthenticators\\(" src/platforms/web/adapters/WebMfaClient.ts src/platforms/native/adapters/NativeMfaClient.ts src/types/parameters.ts -S

Repository: auth0/react-native-auth0

Length of output: 3482


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "getMfaAuthenticators|mfaEnroll\\(|mfaChallenge\\(|mfaVerify\\(" src/platforms/native -S

Repository: auth0/react-native-auth0

Length of output: 2524


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '220,320p' src/platforms/native/bridge/INativeBridge.ts | cat -n
sed -n '260,320p' src/platforms/native/bridge/NativeBridgeManager.ts | cat -n
sed -n '1,220p' android/src/main/java/com/auth0/react/A0Auth0Module.kt | cat -n | sed -n '630,720p'

Repository: auth0/react-native-auth0

Length of output: 6142


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A25 -B10 "getMfaAuthenticators|mfaEnroll|mfaChallenge|mfaVerify" android/src/main/java/com/auth0/react/A0Auth0Module.kt android/src/main/java/com/auth0/react/MfaClient.kt -S

Repository: auth0/react-native-auth0

Length of output: 5203


🌐 Web query:

Auth0 Android MfaClient getAuthenticators factorsAllowed sms voice authenticatorType oobChannel

💡 Result:

In the Auth0 Android SDK, the MfaApiClient is used to handle multi-factor authentication (MFA) flows, including retrieving available authenticators [1][2]. The getAuthenticators method allows you to fetch a list of authenticators enrolled for a user that are valid for the current MFA challenge [1]. You can use the factorsAllowed parameter to filter these results by providing a list of accepted factor types [1]. Key details regarding the factors and types include: 1. FactorsAllowed: This parameter expects a list of strings representing the MFA factor types, such as "otp", "sms", "voice", or "push" [1][2]. These types are typically extracted from the mfaRequirements payload returned when an authentication attempt fails with an mfa_required error [2]. 2. Authenticator Types and OOB Channels: - When an authenticator has the type "oob" (Out-of-Band), it uses an oob_channel to deliver the challenge [3][4]. - Common values for oob_channel include "sms" and "voice" [3][4]. - When a user enrolls a phone number for SMS or voice, Auth0 may create separate authenticator entries for each if both are enabled [3]. - When triggering a challenge for an OOB factor, the oobCode and bindingCode are typically required for verification [1]. For implementation, ensure your application has the "MFA" grant type enabled in your Auth0 application settings [2][5]. The flow generally involves: - Capturing the mfa_required exception during login [2]. - Extracting the mfaToken and available challenge types from the error payload [2]. - Calling mfaClient.getAuthenticators(factorsAllowed = challengeTypes) [1]. - Initiating a challenge with mfaClient.challenge(authenticatorId = ...) [1]. - Verifying the user's input with mfaClient.verify(...) [1]. For complex UI requirements, consider using the auth0-ui-components-android library, which provides pre-built Jetpack Compose components for managing MFA enrollment and verification [6].

Citations:


🌐 Web query:

site:github.com/auth0/Auth0.Android getAuthenticators factorsAllowed voice sms MfaClient

💡 Result:

In the Auth0 Android SDK, the MfaClient.getAuthenticators method is used during the multi-factor authentication (MFA) challenge flow to retrieve a list of authenticators that a user has previously enrolled [1]. The factorsAllowed parameter is an optional list (in Kotlin, a List) that filters the retrieved authenticators to include only those that match the allowed factor types specified by your application's MFA requirements [2][1]. When you receive an mfa_required error during authentication, the error payload contains specific requirements, including which factors (e.g., 'sms', 'voice', or 'otp') are permitted for the current authentication attempt [3][1]. You map these required types to a list and pass them to getAuthenticators to ensure the user is only presented with appropriate options [2][1]. Example usage in Kotlin: val factorTypes = listOf("sms", "voice") // Map from mfaRequirements mfaClient.getAuthenticators(factorsAllowed = factorTypes).start(object: Callback<List, MfaListAuthenticatorsException> { override fun onSuccess(authenticators: List) { // Display authenticators for user to choose } override fun onFailure(exception: MfaListAuthenticatorsException) { // Handle error } }) If you are using Java, you must provide an ArrayList for the factorsAllowed parameter [1]. This API is part of the modern MfaApiClient, which replaced deprecated MFA methods in the AuthenticationAPIClient [4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,180p' src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts | cat -n

Repository: auth0/react-native-auth0

Length of output: 6850


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect MFA types/tests for how factorsAllowed and voice are intended to flow.
sed -n '1,120p' src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts | cat -n
sed -n '1,120p' src/types/parameters.ts | cat -n
sed -n '1,120p' src/types/common.ts | cat -n

Repository: auth0/react-native-auth0

Length of output: 14237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A6 -B6 "factorsAllowed|voice|sms|oob|getAuthenticators" src/platforms/native/adapters/__tests__ src/types src/platforms/native -S

Repository: auth0/react-native-auth0

Length of output: 50382


🌐 Web query:

Auth0.Android getAuthenticators factors list voice sms oob authenticatorType oobChannel docs

💡 Result:

In the Auth0.Android SDK and associated Auth0 MFA APIs, authentication factors are categorized by their type, which determines how a user proves possession [1][2]. The primary authenticator_type values relevant to your query are otp (One-Time Password) and oob (Out-of-Band) [1][3][2]. When the authenticator_type is set to oob, you must specify the oob_channel parameter to define the delivery mechanism for the verification challenge [4][3]. The supported oob_channel values for phone-based MFA are: - sms: Sends a one-time code via SMS [4][5]. - voice: Sends a one-time code via a voice call [4][5]. To manage these factors programmatically—such as enrolling a new factor or requesting a challenge—you interact with the Auth0 Authentication API [1][6]. For example, when associating an MFA authenticator, you provide the authenticator_types (e.g., ["oob"]) and the corresponding oob_channels (e.g., ["sms"] or ["voice"]) [4][3]. While the AuthenticationAPIClient in the Android SDK provides methods to request MFA challenges, it is recommended to check the specific implementation in your version of the SDK [1][2]. Some older methods in the SDK are deprecated in favor of newer API patterns [1]. Always refer to the official Auth0 documentation for the most current handling of MFA flows, as configurations may vary based on your tenant's specific MFA settings and subscription plan [4][5][7].

Citations:


🌐 Web query:

Auth0.Android MfaEnrollmentType.Phone voice sms oob channel docs

💡 Result:

In the context of Auth0 and the Auth0.Android SDK, multi-factor authentication (MFA) involving phone numbers is handled through the "oob" (Out-of-Band) authenticator type [1][2]. 1. MFA Factors and Channels The "oob" (Out-of-Band) authenticator type is used for MFA challenges that occur outside the primary authentication channel, such as SMS or voice phone calls [1][2]. When enrolling a phone number, the oob_channels parameter identifies the specific method used to send the verification code, with accepted values being "sms" or "voice" [1]. 2. Auth0.Android SDK Implementation The Auth0.Android SDK provides dedicated methods to enroll and manage these phone-based factors [3]. Specifically, you can use the enrollPhone method, which requires the user's phone number and a PhoneAuthenticationMethodType to specify the channel (e.g., PhoneAuthenticationMethodType.SMS) [3]. Example (Kotlin): myAccountClient.enrollPhone("+11234567890", PhoneAuthenticationMethodType.SMS).start(object: Callback<EnrollmentChallenge, MyAccountException> { override fun onSuccess(result: EnrollmentChallenge) { // OTP sent. Use result.id and result.authSession to verify. } override fun onFailure(error: MyAccountException) { } }) 3. Key Concepts * Authenticator Type: Set to "oob" when performing MFA via SMS or Voice [1][4]. * OOB Channel: Specifies the delivery mechanism ("sms" or "voice") [1]. * Enrollment Flow: This is typically a two-step process: first, you request an enrollment challenge (which triggers the code delivery), and second, you verify that challenge using the code received by the user [3]. For additional context, Auth0 also supports "push" notifications and "otp" (one-time password) as distinct MFA factors, which are managed differently than the phone-based OOB channels [5][6]. Always ensure the MFA grant type is enabled in your Auth0 Dashboard application settings before attempting to use these flows [3][7].

Citations:


🌐 Web query:

site:auth0.com docs Auth0 Android MfaEnrollmentType.Phone sms voice phone enrollment

💡 Result:

In Auth0 development, SMS and voice MFA are categorized as phone-based authenticators. While there is no specific MfaEnrollmentType.Phone constant in the standard Auth0.Android library, these factors are handled via the Auth0 Universal Login flow or the MFA API using the oob (Out-of-Band) authenticator type [1][2]. When implementing MFA enrollment for phone-based methods: 1. Identification: SMS and voice are treated as separate delivery channels (often referred to as oob_channels in API contexts) [1][3]. 2. Implementation: - Universal Login: Auth0 handles the MFA enrollment UI automatically. If configured, users can choose between SMS or voice delivery methods when enrolling their phone number [4][5][3]. - Custom Flows: If you are building a custom UI, you must interact with the MFA API [2]. When enrolling, you submit the phone number and specify the oob_channels as either sms or voice [1][3]. 3. Data Handling: If both SMS and voice are enabled, Auth0 will automatically create two distinct authenticators for the same phone number (one for each method) upon enrollment [2]. For Android-specific applications, the recommended approach is to use the Universal Login experience via the WebAuthProvider in the auth0-android SDK, which manages these MFA transitions natively without requiring custom enrollment logic for individual factors [6]. If you are working with specialized push-notification-based MFA, the Guardian SDK for Android is used instead for device-bound enrollments [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A4 -B4 "voice" android README.md EXAMPLES.md src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts src/types/parameters.ts -S

Repository: auth0/react-native-auth0

Length of output: 12151


Don't map voice to sms in mapFactorType(). factorsAllowed can include voice, and collapsing it to sms makes getAuthenticators() return SMS authenticators for voice-only flows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/src/main/java/com/auth0/react/MfaClient.kt` around lines 58 - 68, The
factor normalization in mapFactorType() is incorrectly collapsing voice into
sms, which causes voice-only options to be exposed as SMS authenticators. Update
mapFactorType() in MfaClient to preserve voice as its own factor instead of
mapping it to sms, while keeping the existing mappings for otp, sms, email,
push, oob, and recovery-code unchanged.
example/src/App.web.tsx (1)

453-456: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Don’t send TOTP enrollment secrets to a third-party QR service.

barcodeUri contains the TOTP secret; embedding it in https://api.qrserver.com/... leaks that secret off-device. Use an in-app/local QR renderer or omit the QR image and show only the deep link/secret.

Also applies to: 1032-1035

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/src/App.web.tsx` around lines 453 - 456, The QR image generation in
the App.web.tsx TOTP enrollment flow is leaking the secret by sending
enrollmentChallenge.barcodeUri to a third-party service. Update the Image usage
in the enrollment UI to avoid remote QR generation: use a local/in-app QR
renderer or remove the QR image entirely and present only the deep link/secret,
and apply the same change anywhere else the same pattern appears in the
enrollment screen.
example/src/screens/class-based/ClassApiTests.tsx (2)

153-153: 🎯 Functional Correctness | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "interface IMfaClient|type MfaEnroll|interface MfaEnroll|factorType|\\.enroll\\(" src example -C 3

Repository: auth0/react-native-auth0

Length of output: 21896


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant screen and the helper used by the test buttons.
sed -n '1,260p' example/src/screens/class-based/ClassApiTests.tsx
printf '\n--- runTest search ---\n'
rg -n "function runTest|const runTest|runTest\\(" example/src/screens/class-based/ClassApiTests.tsx example/src -C 3
printf '\n--- IMfaClient contract ---\n'
sed -n '1,220p' src/core/interfaces/IMfaClient.ts

Repository: auth0/react-native-auth0

Length of output: 33959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant screen and the helper used by the test buttons.
sed -n '1,260p' example/src/screens/class-based/ClassApiTests.tsx
printf '\n--- runTest search ---\n'
rg -n "function runTest|const runTest|runTest\\(" example/src/screens/class-based/ClassApiTests.tsx example/src -C 3
printf '\n--- IMfaClient contract ---\n'
sed -n '1,220p' src/core/interfaces/IMfaClient.ts

Repository: auth0/react-native-auth0

Length of output: 33959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check what MFA verify returns and whether it contains credential material.
sed -n '1,220p' src/types/common.ts
printf '\n--- MfaVerifyParameters ---\n'
sed -n '220,340p' src/types/parameters.ts

Repository: auth0/react-native-auth0

Length of output: 10921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check what MFA verify returns and whether it contains credential material.
sed -n '1,220p' src/types/common.ts
printf '\n--- MfaVerifyParameters ---\n'
sed -n '220,340p' src/types/parameters.ts

Repository: auth0/react-native-auth0

Length of output: 10921


Use factorType for MFA enrollment.

  • IMfaClient.enroll expects factorType; type does not match the contract and will fail type-checking.
  • The nearby mfa().verify() call returns credentials, so routing it through runTest exposes tokens in the console/UI.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/src/screens/class-based/ClassApiTests.tsx` at line 153, The MFA
enrollment call in ClassApiTests is using the wrong property name and the
verification flow is leaking returned credentials through runTest. Update the
auth0.mfa().enroll invocation to use factorType instead of type, and adjust the
nearby mfa().verify path so it is not wrapped in runTest when it returns
credentials; keep the tokens out of console/UI exposure by handling that result
internally in the test helper or directly awaiting it.

182-185: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact credentials returned by MFA verification before logging/rendering.

runTest logs and displays the raw result, so mfa().verify() can expose access, ID, or refresh tokens. Return a sanitized summary instead. As per coding guidelines, “Never log access tokens, refresh tokens, or ID tokens in error messages or debug output.”

Proposed fix
-              runTest(() => auth0.mfa().verify({ mfaToken, otp }), 'Verify OTP')
+              runTest(async () => {
+                const credentials = await auth0.mfa().verify({ mfaToken, otp });
+                return {
+                  success: true,
+                  tokenType: credentials.tokenType,
+                  expiresAt: credentials.expiresAt,
+                };
+              }, 'Verify OTP')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          <Button
            onPress={() =>
              runTest(async () => {
                const credentials = await auth0.mfa().verify({ mfaToken, otp });
                return {
                  success: true,
                  tokenType: credentials.tokenType,
                  expiresAt: credentials.expiresAt,
                };
              }, 'Verify OTP')
            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/src/screens/class-based/ClassApiTests.tsx` around lines 182 - 185,
The MFA verification flow in ClassApiTests and the runTest helper is exposing
raw credential-bearing results from auth0.mfa().verify(), which can leak
sensitive tokens into logs and UI. Update the Verify OTP path to sanitize the
returned value before passing it to runTest, and ensure runTest only
logs/displays a redacted summary rather than the full verification response. Use
the auth0.mfa().verify call site and runTest as the key places to apply the
redaction.

Source: Coding guidelines

example/src/screens/class-based/ClassLogin.tsx (2)

259-272: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Persist and navigate after successful MFA verification.

This is a login flow, but after mfaClient.verify() succeeds it only updates local wizard state. Save the credentials and navigate like the password-login success path, otherwise the user completes MFA but remains unauthenticated in the app flow.

Proposed fix
       setResult({
         success: true,
         accessToken: credentials.accessToken.substring(0, 20) + '...',
       });
+      await auth0.credentialsManager.saveCredentials(credentials);
+      navigation.replace('ClassProfile', { credentials });
       setMfaStep('complete');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (oobCode) {
        credentials = await mfaClient.verify({
          mfaToken,
          oobCode,
          bindingCode: verifyBindingCode || undefined,
        });
      } else {
        credentials = await mfaClient.verify({ mfaToken, otp: verifyCode });
      }
      setResult({
        success: true,
        accessToken: credentials.accessToken.substring(0, 20) + '...',
      });
      await auth0.credentialsManager.saveCredentials(credentials);
      navigation.replace('ClassProfile', { credentials });
      setMfaStep('complete');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/src/screens/class-based/ClassLogin.tsx` around lines 259 - 272, After
mfaClient.verify succeeds in ClassLogin, the flow only updates local state and
never completes authentication. Update the MFA success branch to persist the
returned credentials and trigger the same post-login navigation used by the
password-login success path, rather than only calling setResult and
setMfaStep('complete'). Use the existing login success handling in ClassLogin as
the reference so the user is fully authenticated and redirected after MFA.

418-421: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Don’t send TOTP enrollment secrets to a third-party QR service.

barcodeUri contains the TOTP secret; embedding it in https://api.qrserver.com/... leaks that secret off-device. Use an in-app/local QR renderer or omit the QR image and show only the deep link/secret.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/src/screens/class-based/ClassLogin.tsx` around lines 418 - 421, The
QR image generation in ClassLogin’s Image source is leaking the TOTP secret by
sending enrollmentChallenge.barcodeUri to a third-party service. Replace the
api.qrserver.com URL usage with an in-app/local QR renderer, or remove the QR
image entirely and present only the deep link or secret locally. Keep the fix
within the ClassLogin component where the enrollment QR is rendered so the
secret never leaves the device.
example/src/screens/hooks/Home.tsx (1)

482-485: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Don’t send TOTP enrollment secrets to a third-party QR service.

barcodeUri contains the TOTP secret; embedding it in https://api.qrserver.com/... leaks that secret off-device. Use an in-app/local QR renderer or omit the QR image and show only the deep link/secret.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/src/screens/hooks/Home.tsx` around lines 482 - 485, The QR image
generation in Home uses the third-party api.qrserver.com URL with
enrollmentChallenge.barcodeUri, which leaks the TOTP secret off-device. Update
the Image rendering path to avoid sending barcodeUri to an external service;
instead use an in-app/local QR renderer, or remove the QR image and present only
the deep link/secret in this screen. Keep the fix localized around the
enrollmentChallenge.barcodeUri usage in Home.tsx.
EXAMPLES-WEB.md (2)

179-205: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove token/secret logging from MFA examples.

Line 191/192 and Line 204 log sensitive MFA material (secret, accessToken) to console. Docs should avoid demonstrating this pattern to prevent credential leakage in real apps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@EXAMPLES-WEB.md` around lines 179 - 205, The MFA example in the documentation
is logging sensitive credentials, so update the example flow in the mfa
enrollment and verification snippets to stop printing the TOTP secret and access
token to the console. Keep the surrounding MfaError handling and the
enrollTotp/verifyOtp examples intact, but replace the console output in the
challenge.type === 'totp' and successful verify paths with non-sensitive
guidance or remove those logs entirely.

275-276: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Web direct-auth support note conflicts with the support matrix.

Line 275 says web supports auth.passwordRealm(), but the README support table marks auth.passwordRealm() as not supported on web. Please align these docs to one authoritative behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@EXAMPLES-WEB.md` around lines 275 - 276, The web direct-auth note conflicts
with the support matrix for auth.passwordRealm(), so update the documentation to
state one consistent behavior across both places. Use the relevant auth method
references in EXAMPLES-WEB.md, especially auth.passwordRealm(),
auth.createUser(), auth.resetPassword(), and MFA Flexible Factors Grant, and
either remove the web support claim or adjust the matrix so both docs match the
same supported/unsupported status.
EXAMPLES.md (1)

1818-1820: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid logging MFA secrets/codes/tokens in sample code.

These lines print sensitive values (secret, oobCode, accessToken) and can leak credentials in app/device logs. Please switch examples to redacted or non-sensitive status messages.

Also applies to: 1842-1842, 1858-1858, 1870-1870

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@EXAMPLES.md` around lines 1818 - 1820, The sample MFA logging in EXAMPLES.md
is printing sensitive values, so update the affected example blocks to avoid
outputting secrets, codes, or tokens. In the challenge-handling examples
(including the code paths that reference challenge.secret, oobCode, and
accessToken), replace the current console.log statements with redacted or
generic status messages that confirm progress without exposing credentials. Use
the relevant example sections around the MFA challenge flows to locate and
update all affected snippets.
ios/A0MfaClient.swift (1)

77-95: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle voice enrollment separately.

Line 78 routes .voice through the same enroll(...phoneNumber:) call as .phone, so a JS request with factorType: 'voice' loses the channel selection on iOS while Android keeps it distinct. That means voice enrollment will behave like SMS here unless you call a voice-specific native path or reject voice as unsupported on iOS.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/A0MfaClient.swift` around lines 77 - 95, The `.voice` case in
`A0MfaClient.enroll` is currently sharing the same
`enroll(mfaToken:phoneNumber:)` path as `.phone`, which drops the channel
distinction for iOS. Update the `switch factorType` handling so `voice` is
either routed through a voice-specific native enrollment path in `mfaClient` or
explicitly rejected as unsupported, while keeping `.phone` on the
SMS/phone-number flow. Use the existing `factorType` switch and
`mfaClient.enroll` call site to locate the change.
src/core/models/__tests__/ErrorCodes.spec.ts (1)

257-265: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid as any casts in the overlap checks.

The includes(code as any) casts suppress the type mismatch between the differently-typed literal arrays. Typing the comparison arrays as string[] (or casting once) keeps the test type-safe.

♻️ Suggested fix
-      const mfaArray = Object.values(MfaErrorCodes);
-      const webAuthArray = Object.values(WebAuthErrorCodes);
-      const credentialsArray = Object.values(CredentialsManagerErrorCodes);
-      const dpopArray = Object.values(DPoPErrorCodes);
+      const mfaArray: string[] = Object.values(MfaErrorCodes);
+      const webAuthArray: string[] = Object.values(WebAuthErrorCodes);
+      const credentialsArray: string[] = Object.values(
+        CredentialsManagerErrorCodes
+      );
+      const dpopArray: string[] = Object.values(DPoPErrorCodes);
 
       const webAuthOverlaps = mfaArray.filter((code) =>
-        webAuthArray.includes(code as any)
+        webAuthArray.includes(code)
       );
       const credentialsOverlaps = mfaArray.filter((code) =>
-        credentialsArray.includes(code as any)
+        credentialsArray.includes(code)
       );
       const dpopOverlaps = mfaArray.filter((code) =>
-        dpopArray.includes(code as any)
+        dpopArray.includes(code)
       );

As per coding guidelines: "Avoid using any types; use strict TypeScript typing instead".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      const mfaArray: string[] = Object.values(MfaErrorCodes);
      const webAuthArray: string[] = Object.values(WebAuthErrorCodes);
      const credentialsArray: string[] = Object.values(
        CredentialsManagerErrorCodes
      );
      const dpopArray: string[] = Object.values(DPoPErrorCodes);

      const webAuthOverlaps = mfaArray.filter((code) =>
        webAuthArray.includes(code)
      );
      const credentialsOverlaps = mfaArray.filter((code) =>
        credentialsArray.includes(code)
      );
      const dpopOverlaps = mfaArray.filter((code) =>
        dpopArray.includes(code)
      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/models/__tests__/ErrorCodes.spec.ts` around lines 257 - 265, The
overlap checks in ErrorCodes.spec use `includes(code as any)`, which hides a
type mismatch with the literal arrays. Update the test data types so
`webAuthArray`, `credentialsArray`, and `dpopArray` are typed compatibly (for
example as `string[]` or a shared string union) and then call `includes` without
`as any` in the `mfaArray.filter` checks. Keep the fix local to the overlap test
block and preserve the existing `webAuthOverlaps`, `credentialsOverlaps`, and
`dpopOverlaps` assertions.

Source: Coding guidelines

src/core/models/MfaError.ts (1)

70-99: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

invalid_grantINVALID_OTP can mis-categorize expired/invalid MFA tokens.

invalid_grant is a generic OAuth error reused across MFA grant responses (e.g. expired or invalid mfa_token, not only a wrong OTP). Since this map is shared by enroll/challenge/verify, an expired-token invalid_grant will be surfaced as INVALID_OTP, hiding the dedicated EXPIRED_MFA_TOKEN / INVALID_MFA_TOKEN categories. Consider distinguishing via error_description/json before defaulting to INVALID_OTP.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/models/MfaError.ts` around lines 70 - 99, The `ERROR_CODE_MAP` in
`MfaError` is mapping the generic `invalid_grant` response directly to
`MfaErrorCodes.INVALID_OTP`, which can misclassify expired or invalid MFA
tokens. Update the MFA error parsing logic in `MfaError` so `invalid_grant` is
not blindly normalized in the same way for all flows; inspect the accompanying
`error_description` or parsed JSON/error payload first, then map token-expiry or
token-invalid cases to `EXPIRED_MFA_TOKEN` or `INVALID_MFA_TOKEN`, and only fall
back to `INVALID_OTP` when the response clearly indicates an OTP failure.
src/factory/Auth0ClientFactory.web.ts (1)

26-26: 🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect getSpaClient and any reset/recreate logic in the web client.
fd -t f 'WebAuth0Client.ts' src/platforms/web | xargs -I{} sh -c 'echo "== {} =="; rg -nP -C3 "getSpaClient|spaClient|resetSpaClientSingleton" {}'
# Check whether the factory or provider resets the spa singleton on signature change.
rg -nP -C3 "resetSpaClientSingleton|getSpaClient" src

Repository: auth0/react-native-auth0

Length of output: 5066


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/factory/Auth0ClientFactory.web.ts =="
sed -n '1,220p' src/factory/Auth0ClientFactory.web.ts | cat -n

echo
echo "== relevant WebAuth0Client constructor context =="
sed -n '80,130p' src/platforms/web/adapters/WebAuth0Client.ts | cat -n

Repository: auth0/react-native-auth0

Length of output: 4202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/factory/Auth0ClientFactory.web.ts =="
sed -n '1,220p' src/factory/Auth0ClientFactory.web.ts | cat -n

echo
echo "== relevant WebAuth0Client constructor context =="
sed -n '80,130p' src/platforms/web/adapters/WebAuth0Client.ts | cat -n

Repository: auth0/react-native-auth0

Length of output: 4202


Key the web spa-js singleton by config signature

Auth0ClientFactory.web.ts now caches by signature, but WebAuth0Client.getSpaClient() is still a process-wide singleton that always returns the first Auth0Client and ignores later clientOptions. A new tenant/config will still reuse the stale spa-js client behind the fresh WebAuth0Client, so web tenant switching won’t work as intended. Key or reset the singleton when the signature changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/factory/Auth0ClientFactory.web.ts` at line 26, The web SPA client
singleton in WebAuth0Client.getSpaClient() is still shared across all configs,
so a new Auth0ClientFactory.web.ts signature can still end up reusing the first
Auth0Client. Update the singleton handling in WebAuth0Client and its
getSpaClient() path so it is keyed by the same config signature used by
getConfigSignature(options), or reset/recreate the cached spa-js client whenever
the signature changes. Make sure the cache invalidation logic is tied to the
existing Auth0ClientFactory.web.ts singleton flow so tenant/config switching
returns the correct Auth0Client.
src/hooks/Auth0Provider.tsx (1)

52-58: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reset auth state when the tenant signature changes.

Recreating client on configSignature changes is correct, but the reducer state still contains the previous client’s user until the async initialization effect completes. For multi-tenant switching, clear the current user/error and set loading before initializing the new client to avoid briefly rendering the wrong tenant session.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/Auth0Provider.tsx` around lines 52 - 58, Reset the auth state when
the tenant signature changes in Auth0Provider so the old client’s user/error do
not persist while the new Auth0 instance initializes. Update the state
management around the reducer and the initialization effect tied to
configSignature to clear the current user and error and set loading true before
starting the new client setup. Use the Auth0Provider, getConfigSignature, and
the async initialization effect that follows the useMemo client creation as the
places to apply the reset.
src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts (1)

5-27: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Make the bridge mock partial or complete.

jest.Mocked<INativeBridge> requires every method on INativeBridge, but this object only provides the MFA-related subset plus a few existing methods. Under strict TS this will fail compilation; use a typed Pick/partial mock for the methods this test exercises.

Proposed fix
-const mockBridge: jest.Mocked<INativeBridge> = {
+const mockBridge = {
   getMfaAuthenticators: jest.fn(),
   mfaEnroll: jest.fn(),
   mfaChallenge: jest.fn(),
   mfaVerify: jest.fn(),
-  initialize: jest.fn(),
-  hasValidInstance: jest.fn(),
-  getBundleIdentifier: jest.fn(),
-  authorize: jest.fn(),
-  clearSession: jest.fn(),
-  cancelWebAuth: jest.fn(),
-  saveCredentials: jest.fn(),
-  getCredentials: jest.fn(),
-  hasValidCredentials: jest.fn(),
-  clearCredentials: jest.fn(),
-  resumeWebAuth: jest.fn(),
-  getDPoPHeaders: jest.fn(),
-  clearDPoPKey: jest.fn(),
-  getSSOCredentials: jest.fn(),
-  getApiCredentials: jest.fn(),
-  clearApiCredentials: jest.fn(),
-  customTokenExchange: jest.fn(),
-};
+} as jest.Mocked<
+  Pick<
+    INativeBridge,
+    'getMfaAuthenticators' | 'mfaEnroll' | 'mfaChallenge' | 'mfaVerify'
+  >
+>;
-    client = new NativeMfaClient(mockBridge);
+    client = new NativeMfaClient(mockBridge as unknown as INativeBridge);

As per coding guidelines, src/**/*.ts: "All TypeScript code must be written in strict mode".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const mockBridge = {
  getMfaAuthenticators: jest.fn(),
  mfaEnroll: jest.fn(),
  mfaChallenge: jest.fn(),
  mfaVerify: jest.fn(),
} as jest.Mocked<
  Pick<
    INativeBridge,
    'getMfaAuthenticators' | 'mfaEnroll' | 'mfaChallenge' | 'mfaVerify'
  >
>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platforms/native/adapters/__tests__/NativeMfaClient.spec.ts` around lines
5 - 27, The `mockBridge` declaration in `NativeMfaClient.spec` is too strongly
typed for the subset of `INativeBridge` methods it actually provides, which
breaks strict TypeScript compilation. Update the test mock to use a
partial/selected type instead of `jest.Mocked<INativeBridge>`, keeping only the
methods exercised by the MFA tests (and any already-used helpers) while
preserving proper Jest mocks. Use the `INativeBridge` interface and the
`mockBridge` constant as the key references when adjusting the type.

Source: Coding guidelines

src/platforms/native/adapters/NativeAuth0Client.ts (1)

141-145: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

The native re-sync is not atomic across sibling clients.

syncLock is instance-scoped, and it's released before originalMethod.apply(...) runs. Because the native module is shared process-wide, two NativeAuth0Client instances can still interleave initialize() and the actual bridge call, so one request can execute against the other tenant's config. The direct getDPoPHeaders path also bypasses this guard entirely, which leaves sender-constrained requests exposed to the same drift.

Suggested direction
-  private syncLock: Promise<void> = Promise.resolve();
+  private static nativeCallLock: Promise<void> = Promise.resolve();

-  private syncNativeConfig(): Promise<void> {
-    this.syncLock = this.syncLock
-      .catch(() => undefined)
-      .then(() => this.initialize(this.bridge, this.options));
-    return this.syncLock;
-  }
+  private runWithNativeConfig<T>(op: () => Promise<T>): Promise<T> {
+    const run = NativeAuth0Client.nativeCallLock
+      .catch(() => undefined)
+      .then(async () => {
+        await this.initialize(this.bridge, this.options);
+        return op();
+      });
+
+    NativeAuth0Client.nativeCallLock = run.then(
+      () => undefined,
+      () => undefined
+    );
+
+    return run;
+  }

-        await this.syncNativeConfig();
-        return originalMethod.apply(bridge, args);
+        return this.runWithNativeConfig(() =>
+          originalMethod.apply(bridge, args)
+        );

Also applies to: 205-208

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platforms/native/adapters/NativeAuth0Client.ts` around lines 141 - 145,
The native config refresh in NativeAuth0Client is only protected by an
instance-level syncLock, so sibling clients can still interleave initialize()
and the subsequent bridge call and run against the wrong tenant config. Update
the shared native access path in NativeAuth0Client so the re-sync and the
original bridge invocation are serialized with a process-wide guard, and make
sure the direct getDPoPHeaders flow uses the same protection instead of
bypassing syncNativeConfig().

Base automatically changed from chore/upgrade-react-native-0.86 to master June 24, 2026 10:37
…into feat/flexible-factors-grant-support

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 23-25: The checkout step is still persisting the GitHub token in
git config, which can expose credentials to later release workflow steps. Update
the actions/checkout usage in the release workflow to set persist-credentials to
false alongside fetch-depth so the token is not retained during build or scanner
execution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a551cdd-6301-44ab-a280-a7bad6761d20

📥 Commits

Reviewing files that changed from the base of the PR and between 442d1af and 9d26afc.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • .github/workflows/release.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 23-25: The checkout step is still persisting the GitHub token in
git config, which can expose credentials to later release workflow steps. Update
the actions/checkout usage in the release workflow to set persist-credentials to
false alongside fetch-depth so the token is not retained during build or scanner
execution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a551cdd-6301-44ab-a280-a7bad6761d20

📥 Commits

Reviewing files that changed from the base of the PR and between 442d1af and 9d26afc.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • .github/workflows/release.yml
🛑 Comments failed to post (1)
.github/workflows/release.yml (1)

23-25: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable credential persistence in checkout step.

actions/checkout currently keeps the GitHub token in git config for later steps. Set persist-credentials: false to reduce token exposure during build/scanner execution.

Suggested patch
       - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
         with:
           fetch-depth: 0
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0
          persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 23-25: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 23 - 25, The checkout step is
still persisting the GitHub token in git config, which can expose credentials to
later release workflow steps. Update the actions/checkout usage in the release
workflow to set persist-credentials to false alongside fetch-depth so the token
is not retained during build or scanner execution.

Source: Linters/SAST tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/types/common.ts (1)

241-245: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repo’s platform-only JSDoc notation here.

Lines 242-244 and Line 310 describe platform differences in prose (On Android, Native (iOS/Android)), but these exported API docs are supposed to use the explicit platform markers instead (for example, **Android only** / **Web only**). As per coding guidelines, "Document platform-specific behavior in JSDoc with platform name only notation (e.g., iOS only, Android only)".

Also applies to: 310-310

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/common.ts` around lines 241 - 245, The exported API JSDoc in the
common types docs uses prose for platform differences instead of the repo’s
required platform-only notation. Update the affected remarks blocks in the
shared type documentation to use explicit markers like “Android only” or “Web
only” (and the platform-specific note near the Native iOS/Android entry) rather
than “On Android” or “Native (iOS/Android)”. Keep the existing meaning, but
rewrite the JSDoc so the platform scope is stated only with the standardized
platform tags.

Source: Coding guidelines

src/platforms/web/adapters/WebMfaClient.ts (1)

76-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid explicit any in the catch clause.

catch (inner: any) violates the project's strict-typing rule. Prefer unknown and narrow before accessing .error. The same pattern recurs in the other catch blocks (enroll, challenge, verify); consider applying consistently.

♻️ Suggested narrowing
-      } catch (inner: any) {
+      } catch (inner: unknown) {
         // Only synthesize a context when spa-js reports it is missing; this
         // preserves a real context populated by spa-js's own login flow.
-        if (inner?.error !== 'invalid_request') throw inner;
+        if ((inner as { error?: string })?.error !== 'invalid_request')
+          throw inner;

As per coding guidelines: "Avoid using any types; use strict TypeScript typing instead".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/platforms/web/adapters/WebMfaClient.ts` around lines 76 - 123, The catch
clauses in WebMfaClient use an explicit any, which violates strict TypeScript
typing. Update the inner catch in getAuthenticators, and the similar catch
blocks in enroll, challenge, and verify, to use unknown instead, then narrow the
thrown value before reading .error. Keep the existing invalid_request fallback
behavior intact by safely checking the narrowed value in WebMfaClient methods.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@EXAMPLES.md`:
- Around line 1843-1845: The OOB enrollment example is logging the follow-up
verification code, which should not be printed. Update the example around the
challenge.type === 'oob' handling to keep challenge.oobCode only in local state
or the next-step flow instead of sending it to console.log, so the EXAMPLES.md
guidance shows safe in-memory handling.

---

Nitpick comments:
In `@src/platforms/web/adapters/WebMfaClient.ts`:
- Around line 76-123: The catch clauses in WebMfaClient use an explicit any,
which violates strict TypeScript typing. Update the inner catch in
getAuthenticators, and the similar catch blocks in enroll, challenge, and
verify, to use unknown instead, then narrow the thrown value before reading
.error. Keep the existing invalid_request fallback behavior intact by safely
checking the narrowed value in WebMfaClient methods.

In `@src/types/common.ts`:
- Around line 241-245: The exported API JSDoc in the common types docs uses
prose for platform differences instead of the repo’s required platform-only
notation. Update the affected remarks blocks in the shared type documentation to
use explicit markers like “Android only” or “Web only” (and the
platform-specific note near the Native iOS/Android entry) rather than “On
Android” or “Native (iOS/Android)”. Keep the existing meaning, but rewrite the
JSDoc so the platform scope is stated only with the standardized platform tags.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7775b19f-fc18-46d0-9646-035a07b873e7

📥 Commits

Reviewing files that changed from the base of the PR and between 9d26afc and 900f7a3.

📒 Files selected for processing (14)
  • EXAMPLES-WEB.md
  • EXAMPLES.md
  • android/src/main/java/com/auth0/react/MfaClient.kt
  • example/src/App.web.tsx
  • example/src/screens/class-based/ClassApiTests.tsx
  • example/src/screens/class-based/ClassLogin.tsx
  • example/src/screens/hooks/Home.tsx
  • ios/A0MfaClient.swift
  • src/core/models/MfaError.ts
  • src/core/models/__tests__/MfaError.spec.ts
  • src/platforms/web/adapters/WebMfaClient.ts
  • src/platforms/web/adapters/__tests__/WebMfaClient.spec.ts
  • src/types/common.ts
  • src/types/parameters.ts
✅ Files skipped from review due to trivial changes (2)
  • src/platforms/web/adapters/tests/WebMfaClient.spec.ts
  • EXAMPLES-WEB.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/core/models/tests/MfaError.spec.ts
  • src/core/models/MfaError.ts
  • src/types/parameters.ts
  • example/src/screens/class-based/ClassApiTests.tsx
  • ios/A0MfaClient.swift
  • example/src/screens/class-based/ClassLogin.tsx
  • example/src/screens/hooks/Home.tsx
  • android/src/main/java/com/auth0/react/MfaClient.kt
  • example/src/App.web.tsx

Comment thread EXAMPLES.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants