Skip to content

fix: support runtime tenant/domain switching with per-client credential isolation #1564

Merged
subhankarmaiti merged 8 commits into
masterfrom
fix/multi-tenant-domain-switching
Jun 24, 2026
Merged

fix: support runtime tenant/domain switching with per-client credential isolation #1564
subhankarmaiti merged 8 commits into
masterfrom
fix/multi-tenant-domain-switching

Conversation

@subhankarmaiti

@subhankarmaiti subhankarmaiti commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes switching between Auth0 tenants (domain/clientId) at runtime. Previously, changing the domain or clientId did not take effect — calls kept targeting the original tenant — and all clients shared a single native credentials store, so one tenant's session could surface for another.

Credential isolation

Adds an optional credentialsManagerStorageKey to Auth0Options. When set, it namespaces the native credentials store — the Android SharedPreferences file name and the iOS Keychain service — so each client/tenant gets an isolated store instead of sharing one.

  • Omit it (default) → unchanged behavior; existing logged-in users keep their session on upgrade.
  • Set a unique value per additional tenant → isolated credentials; switching tenants no longer surfaces another tenant's session.

Available via both the Auth0Provider prop and the Auth0 class constructor. Works on bare React Native and Expo.

⚠️ Note on re-login

There is no migration between stores. Changing or removing a previously-set key points a client at a different (empty) store, requiring a fresh login. Keep keys stable, and leave the primary tenant without a key. See the "Switching tenants at runtime" section in EXAMPLES.md.

You still need to register each tenant's callback at build time (manifest / Info.plist / Expo plugin).

Test plan

  • Unit tests + typecheck pass.
  • Switch tenant at runtime and confirm authorize() opens the newly selected tenant's login and the redirect resolves.
  • Verified on device: upgrading an existing install kept the prior login (default store), and a new tenant with its own key mapped to a separate credentials manager.

Fixes #1520
Fixes #1523

Summary by CodeRabbit

Release Notes

  • New Features

    • Added credentialsManagerStorageKey to scope native credentials storage (Android SharedPreferences / iOS Keychain) per tenant/client.
    • Native clients now re-initialize automatically when identity-relevant options change, ensuring the active configuration stays in sync.
  • Bug Fixes

    • Improved client caching/remounting so changes to identity-relevant configuration take effect reliably (including when option key order differs).
  • Documentation

    • Expanded “Switching tenants at runtime” with runtime toggling guidance and updated storage-key isolation recommendations.

@coderabbitai

coderabbitai Bot commented Jun 11, 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: 968f060a-bd29-4702-9260-533eba6f3f32

📥 Commits

Reviewing files that changed from the base of the PR and between 66dbea0 and d5f2a5f.

📒 Files selected for processing (1)
  • src/platforms/native/bridge/__tests__/NativeBridgeManager.spec.ts

📝 Walkthrough

Walkthrough

Adds credentialsManagerStorageKey to namespace native credential storage per tenant. Introduces getConfigSignature for signature-based client caching and native re-initialization. Threads the new parameter through iOS and Android native layers and updates docs and tests for tenant switching behavior.

Changes

Multi-tenant credential isolation and config-driven re-initialization

Layer / File(s) Summary
Public API contracts and TurboModule spec
src/types/common.ts, src/platforms/native/bridge/INativeBridge.ts, src/specs/NativeA0Auth0.ts, android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt
Adds credentialsManagerStorageKey?: string to Auth0Options, extends INativeBridge.initialize with the new optional parameter, updates the TurboModule spec, and updates the old-arch Android method signature.
Config signature utility
src/core/utils/configSignature.ts, src/core/utils/index.ts, src/core/utils/__tests__/configSignature.spec.ts
Introduces SIGNIFICANT_KEYS, getConfigSignature, and a recursive normalizer that produces a deterministic, key-order-independent identity string from Auth0Options.
Client factory caching and Auth0Provider re-mount
src/factory/Auth0ClientFactory.ts, src/factory/Auth0ClientFactory.web.ts, src/hooks/Auth0Provider.tsx, src/factory/__tests__/Auth0ClientFactory.spec.ts
Switches both native and web cache keys to getConfigSignature(options). Auth0Provider now recreates the Auth0 client when the signature changes. Factory tests cover useDPoP divergence and key-order equivalence.
NativeAuth0Client multi-tenant config re-sync
src/platforms/native/adapters/NativeAuth0Client.ts
Adds static appliedNativeSignature, per-instance configSignature and syncLock, and syncNativeConfig(). Re-initialization is triggered by either an invalid instance or a drifted signature. The guarded bridge wrapper awaits syncNativeConfig() before every native call.
NativeBridgeManager propagation and NativeAuth0Client tests
src/platforms/native/bridge/NativeBridgeManager.ts, src/platforms/native/adapters/__tests__/NativeAuth0Client.spec.ts
Extends NativeBridgeManager.initialize to forward credentialsManagerStorageKey. Adds test resets for appliedNativeSignature, updates existing assertions for new optional args, and introduces the native config re-sync test suite.
iOS native credential storage namespacing
ios/NativeBridge.swift, ios/A0Auth0.mm, example/ios/Auth0Example.xcodeproj/project.pbxproj
Adds import SimpleKeychain and credentialsManagerStorageKey: String? to NativeBridge's init, conditionally constructing CredentialsManager with a SimpleKeychain(service: key) scope. Threads the parameter through A0Auth0.mm and adds bridging header config to the example app's Xcode project.
iOS CocoaPods SimpleKeychain dependency
A0Auth0.podspec
Adds SimpleKeychain version 1.3.0 as a new CocoaPods dependency.
Android native credential storage namespacing
android/src/main/java/com/auth0/react/A0Auth0Module.kt
Adds credentialsManagerStorageKey to initializeAuth0WithConfiguration, introduces buildStorage to choose default or keyed SharedPreferencesStorage, and updates credential manager initialization paths to use the selected storage instance.
Multi-tenant docs
EXAMPLES.md
Adds runtime tenant switching guidance, credential-isolation examples for hooks and class-based usage, and a scenario table describing when users must log in again.

Sequence Diagram(s)

sequenceDiagram
  participant App as React app
  participant Provider as Auth0Provider
  participant Factory as Auth0ClientFactory
  participant NativeClient as NativeAuth0Client
  participant Bridge as NativeBridgeManager
  participant Native as Native layer

  App->>Provider: render with Auth0Options
  Provider->>Factory: createClient(options)
  Factory->>NativeClient: construct client for config signature
  NativeClient->>Bridge: initialize(..., credentialsManagerStorageKey)
  Bridge->>Native: initializeAuth0WithConfiguration(..., storage key)
  Native-->>Bridge: create namespaced credentials store

  App->>Provider: re-render with changed identity config
  Provider->>Factory: createClient(options')
  Factory->>NativeClient: new client or cache miss
  NativeClient->>NativeClient: syncNativeConfig()
  NativeClient->>Bridge: re-initialize if signature drifted
  NativeClient->>Bridge: invoke bridged native method
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • pmathew92

Poem

🐇 A key for each tenant, a hop for each tune,
signatures sparkle by starlight and moon.
The bridge does a wiggle, then re-syncs just right,
and every store stays cozy and bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 clearly matches the main change: runtime tenant/domain switching plus per-client credential isolation.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/multi-tenant-domain-switching

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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 title fix: support runtime tenant/domain switching fix: support runtime tenant/domain switching with per-client credential isolation Jun 23, 2026
@subhankarmaiti subhankarmaiti marked this pull request as ready for review June 23, 2026 12:03
@subhankarmaiti subhankarmaiti requested a review from a team as a code owner June 23, 2026 12:03

@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: 6

Caution

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

⚠️ Outside diff range comments (2)
src/platforms/native/adapters/NativeAuth0Client.ts (1)

70-73: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

getDPoPHeaders still bypasses native re-sync and can run with stale tenant config.

Lines 201-204 add re-sync in the guarded bridge path, but Line 70-Line 73 and Line 169 call this.bridge.getDPoPHeaders(...) directly. After a sibling client reconfigures the native singleton, this DPoP path can execute against the wrong native config.

🐛 Proposed fix
 const getDPoPHeadersForOrchestrator = async (params: DPoPHeadersParams) => {
   await this.ready;
+  await this.syncNativeConfig();
   return this.bridge.getDPoPHeaders(params);
 };
@@
 async getDPoPHeaders(
   params: DPoPHeadersParams
 ): Promise<Record<string, string>> {
   await this.ready;
+  await this.syncNativeConfig();
   try {
     return await this.bridge.getDPoPHeaders(params);
   } catch (e) {

Also applies to: 164-170, 201-204

🤖 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 70 - 73, The
getDPoPHeadersForOrchestrator function and the getDPoPHeaders call at line 169
are directly invoking bridge methods without triggering the native re-sync
mechanism that is present in the guarded bridge path at lines 201-204. To fix
this, ensure that both the getDPoPHeadersForOrchestrator function and the
getDPoPHeaders call at line 169 use the same re-sync pattern that protects
against stale native configuration. Extract or refactor the re-sync logic from
lines 201-204 and apply it to guard the getDPoPHeaders calls so that the native
configuration is always synchronized before executing these operations.
src/specs/NativeA0Auth0.ts (1)

22-31: 🗄️ Data Integrity & Integration | 🟠 Major

Ensure yarn codegen was executed for the TurboModule spec update.

The credentialsManagerStorageKey argument has been properly propagated across both iOS (ios/A0Auth0.mm, ios/NativeBridge.swift) and Android (A0Auth0Module.kt, A0Auth0Spec.kt) implementations. However, per coding guidelines, TurboModule spec changes require explicitly running yarn codegen to regenerate native bindings—please confirm this was done.

🤖 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/specs/NativeA0Auth0.ts` around lines 22 - 31, After adding the new
`credentialsManagerStorageKey` parameter to the
`initializeAuth0WithConfiguration` method signature in the TurboModule spec, you
must run the codegen command to regenerate the native bindings. Execute `yarn
codegen` from the project root to ensure the native layer (iOS and Android
implementations) are automatically regenerated with the updated method
signature, keeping the TypeScript spec synchronized with the native
implementations.

Source: Coding guidelines

🧹 Nitpick comments (1)
src/core/utils/configSignature.ts (1)

13-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Convert the exported API comment to JSDoc.

Please use a /** ... */ block for getConfigSignature so the public API is documented consistently.

Suggested fix
-// Stable, order-independent identity string for a config: keys the factory cache, the provider memo, and the native re-init decision. Object values are sorted so key order doesn't matter.
+/**
+ * Stable, order-independent identity string for a config.
+ * Keys factory cache, provider memoization, and native re-init drift checks.
+ */
 export function getConfigSignature(options: Auth0Options): string {

As per coding guidelines, "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/core/utils/configSignature.ts` around lines 13 - 14, Convert the
single-line comment above the getConfigSignature function to a JSDoc comment
block. Replace the `//` comment that describes the stable, order-independent
identity string functionality with a `/** ... */` JSDoc block that contains the
same descriptive text. This ensures the public API is documented consistently
using JSDoc format as per coding guidelines.

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/A0Auth0Module.kt`:
- Around line 304-309: In the catch block for the Exception `e` at the
biometrics authentication fallback, the promise.reject call for
BIOMETRICS_AUTHENTICATION_ERROR_CODE is missing the caught exception `e` as a
parameter, which loses critical debugging information including the stack trace
and root cause. Modify the promise.reject invocation to include the caught
exception `e` as the third parameter (throwable) and optionally append
`e.message` to the rejection message string so that the actual error details are
preserved for diagnosing why the Local Authentication Options parsing failed.

In `@EXAMPLES.md`:
- Around line 1958-1960: A blank blockquote line (containing only `>`) between
the two note sections in EXAMPLES.md is triggering the markdownlint MD028 error.
Remove the blank `>` line that separates the "Note: Switching tenants..."
blockquote from the "Important — credentials are shared..." blockquote so they
form a single continuous blockquote without a break in between.

In `@ios/NativeBridge.swift`:
- Line 13: The import of SimpleKeychain in NativeBridge.swift relies on it being
available only as a transitive dependency through Auth0, which creates fragility
in the build. To fix this, add SimpleKeychain as an explicit direct dependency
to the A0Auth0.podspec file by including a dependency declaration such as
s.dependency 'SimpleKeychain', '~> 1.3' (or the appropriate version constraint)
in the podspec alongside the existing Auth0 dependency declaration.

In `@src/core/utils/configSignature.ts`:
- Around line 4-11: The SIGNIFICANT_KEYS array in configSignature.ts is missing
the maxRetries configuration option, which means changes to maxRetries alone
won't trigger a reconfiguration of the native client instance, allowing stale
retry behavior to persist. Add maxRetries to the SIGNIFICANT_KEYS array as a
string literal to ensure it is included in the configuration signature for drift
detection, allowing the native initialization to properly respond to changes in
this value.

In `@src/platforms/native/adapters/__tests__/NativeAuth0Client.spec.ts`:
- Around line 25-28: Replace the `as any` type cast with `as unknown` in the
NativeAuth0Client static reset line where appliedNativeSignature is being set to
null. This maintains strict TypeScript typing guidelines while still allowing
access to the private static property for test cleanup purposes.

In `@src/platforms/native/bridge/INativeBridge.ts`:
- Line 38: The JSDoc parameter documentation for credentialsManagerStorageKey on
line 38 describes platform-specific behavior using inline notation instead of
the required explicit platform-only format. Update the comment to use the
standardized platform notation (e.g., **iOS only**, **Android only**) instead of
inline descriptions like "Android SharedPreferences name / iOS keychain
service". The documentation should clearly indicate which platforms use which
storage mechanism using the explicit platform-only notation format as defined in
the coding guidelines.

---

Outside diff comments:
In `@src/platforms/native/adapters/NativeAuth0Client.ts`:
- Around line 70-73: The getDPoPHeadersForOrchestrator function and the
getDPoPHeaders call at line 169 are directly invoking bridge methods without
triggering the native re-sync mechanism that is present in the guarded bridge
path at lines 201-204. To fix this, ensure that both the
getDPoPHeadersForOrchestrator function and the getDPoPHeaders call at line 169
use the same re-sync pattern that protects against stale native configuration.
Extract or refactor the re-sync logic from lines 201-204 and apply it to guard
the getDPoPHeaders calls so that the native configuration is always synchronized
before executing these operations.

In `@src/specs/NativeA0Auth0.ts`:
- Around line 22-31: After adding the new `credentialsManagerStorageKey`
parameter to the `initializeAuth0WithConfiguration` method signature in the
TurboModule spec, you must run the codegen command to regenerate the native
bindings. Execute `yarn codegen` from the project root to ensure the native
layer (iOS and Android implementations) are automatically regenerated with the
updated method signature, keeping the TypeScript spec synchronized with the
native implementations.

---

Nitpick comments:
In `@src/core/utils/configSignature.ts`:
- Around line 13-14: Convert the single-line comment above the
getConfigSignature function to a JSDoc comment block. Replace the `//` comment
that describes the stable, order-independent identity string functionality with
a `/** ... */` JSDoc block that contains the same descriptive text. This ensures
the public API is documented consistently using JSDoc format as per coding
guidelines.
🪄 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: 7b3a777a-a037-4a09-a37b-d00aad3578f9

📥 Commits

Reviewing files that changed from the base of the PR and between 90e6c45 and 3a2c0ec.

⛔ Files ignored due to path filters (1)
  • example/ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • EXAMPLES.md
  • android/src/main/java/com/auth0/react/A0Auth0Module.kt
  • android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt
  • example/ios/Auth0Example.xcodeproj/project.pbxproj
  • ios/A0Auth0.mm
  • ios/NativeBridge.swift
  • 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/Auth0Provider.tsx
  • src/platforms/native/adapters/NativeAuth0Client.ts
  • src/platforms/native/adapters/__tests__/NativeAuth0Client.spec.ts
  • src/platforms/native/bridge/INativeBridge.ts
  • src/platforms/native/bridge/NativeBridgeManager.ts
  • src/specs/NativeA0Auth0.ts
  • src/types/common.ts

Comment thread android/src/main/java/com/auth0/react/A0Auth0Module.kt
Comment thread EXAMPLES.md
Comment thread ios/NativeBridge.swift
Comment thread src/core/utils/configSignature.ts
Comment thread src/platforms/native/adapters/__tests__/NativeAuth0Client.spec.ts
Comment thread src/platforms/native/bridge/INativeBridge.ts Outdated
pmathew92
pmathew92 previously approved these changes Jun 24, 2026
@subhankarmaiti subhankarmaiti merged commit 65569aa into master Jun 24, 2026
5 checks passed
@subhankarmaiti subhankarmaiti deleted the fix/multi-tenant-domain-switching branch June 24, 2026 10:28
@subhankarmaiti subhankarmaiti mentioned this pull request Jun 26, 2026
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.

webAuth() uses the previously-active tenant's authorize URL Dynamically swapping clientId or domain during runtime isn't working

2 participants