From b049a8a109fcdecd58cf177e6ea24ca99e611779 Mon Sep 17 00:00:00 2001 From: Gabriel Donadel Dall'Agnol Date: Mon, 7 Sep 2026 15:55:13 -0300 Subject: [PATCH 1/3] fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension (#1654) --- android/build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 29a37a4c..d41500de 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -25,7 +25,13 @@ def isNewArchitectureEnabled() { } apply plugin: "com.android.library" -apply plugin: "kotlin-android" +// AGP 9 ships built-in Kotlin support and registers the `kotlin` extension +// itself. Applying the Kotlin plugin on top of it fails configuration with +// "Cannot add extension with name 'kotlin'". Only apply it when nothing has +// registered that extension yet. +if (project.extensions.findByName('kotlin') == null) { + apply plugin: "kotlin-android" +} if (isNewArchitectureEnabled()) { apply plugin: "com.facebook.react" From b3dc4f3cf54c577cd365cc1e89d0a065fba5053d Mon Sep 17 00:00:00 2001 From: Subhankar Maiti <35273200+subhankarmaiti@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:27:08 +0530 Subject: [PATCH 2/3] Merge commit from fork --- src/factory/Auth0ClientFactory.web.ts | 13 ++++- .../__tests__/Auth0ClientFactory.web.spec.ts | 49 +++++++++++++++++++ .../Auth0ClientFactory.web.ssr.spec.ts | 44 +++++++++++++++++ src/platforms/web/adapters/WebAuth0Client.ts | 30 ++---------- .../WebAuth0Client.getDPoPHeaders.spec.ts | 1 - .../adapters/__tests__/WebAuth0Client.spec.ts | 5 -- 6 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 src/factory/__tests__/Auth0ClientFactory.web.spec.ts create mode 100644 src/factory/__tests__/Auth0ClientFactory.web.ssr.spec.ts diff --git a/src/factory/Auth0ClientFactory.web.ts b/src/factory/Auth0ClientFactory.web.ts index 43888d64..3ef3fc59 100644 --- a/src/factory/Auth0ClientFactory.web.ts +++ b/src/factory/Auth0ClientFactory.web.ts @@ -6,10 +6,13 @@ import { validateAuth0Options, getConfigSignature } from '../core/utils'; // This file ONLY imports the Web client. import { WebAuth0Client } from '../platforms/web'; +const isBrowser = + typeof window !== 'undefined' && typeof window.document !== 'undefined'; + /** * Creates the Web-specific IAuth0Client; selected by bundlers when targeting web. - * Clients are cached by config signature so remounts reuse the same instance - * (avoiding duplicate refresh exchanges); a config change yields a fresh client. + * In a browser, clients are cached by config signature so remounts reuse the same + * instance (avoiding duplicate refresh exchanges); a config change yields a fresh client. */ export class Auth0ClientFactory { private static clientCache = new Map(); @@ -23,6 +26,12 @@ export class Auth0ClientFactory { static createClient(options: Auth0Options): IAuth0Client { validateAuth0Options(options); + // Off the browser (SSR, route handlers) the cache would be shared across + // requests, so don't use it. + if (!isBrowser) { + return new WebAuth0Client(options as WebAuth0Options); + } + const cacheKey = getConfigSignature(options); let client = Auth0ClientFactory.clientCache.get(cacheKey); if (!client) { diff --git a/src/factory/__tests__/Auth0ClientFactory.web.spec.ts b/src/factory/__tests__/Auth0ClientFactory.web.spec.ts new file mode 100644 index 00000000..338c619a --- /dev/null +++ b/src/factory/__tests__/Auth0ClientFactory.web.spec.ts @@ -0,0 +1,49 @@ +import { Auth0ClientFactory } from '../Auth0ClientFactory.web'; +import type { WebAuth0Client } from '../../platforms/web'; + +// A real Auth0Client won't construct on jsdom's insecure origin, and we need a +// distinct object per construction to tell shared instances apart. +jest.mock('@auth0/auth0-spa-js', () => ({ + Auth0Client: jest.fn().mockImplementation(() => ({ mfa: {}, passkey: {} })), +})); + +const options = { + domain: 'tenant-a.us.auth0.com', + clientId: 'client-a', + useDPoP: false, +}; + +const spaClientOf = (client: unknown) => (client as WebAuth0Client).client; + +describe('Auth0ClientFactory (web) in a browser', () => { + beforeEach(() => { + Auth0ClientFactory.resetClientCache(); + }); + + it('reuses the cached client when the config has not changed', () => { + const first = Auth0ClientFactory.createClient(options); + const second = Auth0ClientFactory.createClient(options); + + expect(second).toBe(first); + }); + + it('creates a new client when the config signature changes', () => { + const tenantA = Auth0ClientFactory.createClient(options); + const tenantB = Auth0ClientFactory.createClient({ + ...options, + domain: 'tenant-b.eu.auth0.com', + }); + + expect(tenantB).not.toBe(tenantA); + }); + + it('gives the new client its own spa-js client', () => { + const tenantA = Auth0ClientFactory.createClient(options); + const tenantB = Auth0ClientFactory.createClient({ + ...options, + domain: 'tenant-b.eu.auth0.com', + }); + + expect(spaClientOf(tenantB)).not.toBe(spaClientOf(tenantA)); + }); +}); diff --git a/src/factory/__tests__/Auth0ClientFactory.web.ssr.spec.ts b/src/factory/__tests__/Auth0ClientFactory.web.ssr.spec.ts new file mode 100644 index 00000000..f780fd40 --- /dev/null +++ b/src/factory/__tests__/Auth0ClientFactory.web.ssr.spec.ts @@ -0,0 +1,44 @@ +/** + * @jest-environment node + */ +import { Auth0ClientFactory } from '../Auth0ClientFactory.web'; +import type { WebAuth0Client } from '../../platforms/web'; + +const options = { + domain: 'tenant-a.us.auth0.com', + clientId: 'client-a', + useDPoP: false, +}; + +const spaClientOf = (client: unknown) => (client as WebAuth0Client).client; + +describe('Auth0ClientFactory (web) outside a browser', () => { + it('runs without browser globals', () => { + expect(typeof window).toBe('undefined'); + }); + + it('creates a new client on every call', () => { + const first = Auth0ClientFactory.createClient(options); + const second = Auth0ClientFactory.createClient(options); + + expect(second).not.toBe(first); + }); + + it('does not share the spa-js client, so token caches stay separate', () => { + const first = Auth0ClientFactory.createClient(options); + const second = Auth0ClientFactory.createClient(options); + + expect(spaClientOf(second)).not.toBe(spaClientOf(first)); + }); + + it('does not share the spa-js client across tenants', () => { + const tenantA = Auth0ClientFactory.createClient(options); + const tenantB = Auth0ClientFactory.createClient({ + domain: 'tenant-b.eu.auth0.com', + clientId: 'client-b', + useDPoP: false, + }); + + expect(spaClientOf(tenantB)).not.toBe(spaClientOf(tenantA)); + }); +}); diff --git a/src/platforms/web/adapters/WebAuth0Client.ts b/src/platforms/web/adapters/WebAuth0Client.ts index d4194223..8d1039ee 100644 --- a/src/platforms/web/adapters/WebAuth0Client.ts +++ b/src/platforms/web/adapters/WebAuth0Client.ts @@ -53,33 +53,9 @@ export class WebAuth0Client implements IAuth0Client { params: DPoPHeadersParams ) => Promise>; public readonly client: Auth0Client; - private static spaClient: Auth0Client | null = null; private logoutInProgress = false; - /** - * Factory method to get a singleton instance of Auth0Client. - * This ensures that the client is only created once and reused. - * - * @param options - The Auth0ClientOptions to configure the client. - * @returns An instance of Auth0Client. - */ - private static getSpaClient(options: Auth0ClientOptions): Auth0Client { - if (WebAuth0Client.spaClient) { - return WebAuth0Client.spaClient; - } - WebAuth0Client.spaClient = new Auth0Client(options); - return WebAuth0Client.spaClient; - } - - /** - * Reset the singleton instance. Used for testing purposes. - * @internal - */ - public static resetSpaClientSingleton(): void { - WebAuth0Client.spaClient = null; - } - constructor(options: WebAuth0Options) { const baseUrl = `https://${options.domain}`; this.baseUrl = baseUrl; @@ -108,9 +84,9 @@ export class WebAuth0Client implements IAuth0Client { }, }; - // Use the singleton factory to get the spa-js client instance. - const client = WebAuth0Client.getSpaClient(clientOptions); - this.client = client; + // One client per instance: a shared one would leak its token cache + // to the next request on a server. + this.client = new Auth0Client(clientOptions); // Create a bound getDPoPHeaders function for the orchestrator const getDPoPHeadersForOrchestrator = async (params: DPoPHeadersParams) => { diff --git a/src/platforms/web/adapters/__tests__/WebAuth0Client.getDPoPHeaders.spec.ts b/src/platforms/web/adapters/__tests__/WebAuth0Client.getDPoPHeaders.spec.ts index de94fa7f..a1c35e18 100644 --- a/src/platforms/web/adapters/__tests__/WebAuth0Client.getDPoPHeaders.spec.ts +++ b/src/platforms/web/adapters/__tests__/WebAuth0Client.getDPoPHeaders.spec.ts @@ -83,7 +83,6 @@ describe('WebAuth0Client - getDPoPHeaders', () => { beforeEach(() => { jest.clearAllMocks(); - WebAuth0Client.resetSpaClientSingleton(); // Setup window.location mock Object.defineProperty(window, 'location', { diff --git a/src/platforms/web/adapters/__tests__/WebAuth0Client.spec.ts b/src/platforms/web/adapters/__tests__/WebAuth0Client.spec.ts index ca5f981e..0122af1c 100644 --- a/src/platforms/web/adapters/__tests__/WebAuth0Client.spec.ts +++ b/src/platforms/web/adapters/__tests__/WebAuth0Client.spec.ts @@ -130,9 +130,6 @@ describe('WebAuth0Client', () => { // Clear all mocks first jest.clearAllMocks(); - // Reset the singleton to ensure fresh instances - WebAuth0Client.resetSpaClientSingleton(); - // Setup window.location mock Object.defineProperty(window, 'location', { value: { @@ -175,9 +172,7 @@ describe('WebAuth0Client', () => { }); afterEach(() => { - // Clear all mocks and reset singleton jest.clearAllMocks(); - WebAuth0Client.resetSpaClientSingleton(); }); describe('constructor', () => { From 7069a2bae1f236b367009bcef44a3fcd550d0205 Mon Sep 17 00:00:00 2001 From: Subhankar Maiti <35273200+subhankarmaiti@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:03:15 +0530 Subject: [PATCH 3/3] Release v5.11.1 (#1656) --- .version | 2 +- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- src/core/utils/telemetry.ts | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.version b/.version index ff9c6e14..3834a7ed 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v5.11.0 +v5.11.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5052bfc7..61f16bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,25 @@ # Change Log +## [v5.11.1](https://github.com/auth0/react-native-auth0/tree/v5.11.1) (2026-09-08) + +[Full Changelog](https://github.com/auth0/react-native-auth0/compare/v5.11.0...v5.11.1) + +**Deprecated** + +- refactor: deprecate the client-side Management API [\#1624](https://github.com/auth0/react-native-auth0/pull/1624) ([subhankarmaiti](https://github.com/subhankarmaiti)) +- refactor: deprecate the legacy MFA methods on the auth client [\#1625](https://github.com/auth0/react-native-auth0/pull/1625) ([subhankarmaiti](https://github.com/subhankarmaiti)) + +**Fixed** + +- security fixes +- fix(android): skip explicit Kotlin plugin when AGP registers the kotlin extension [\#1654](https://github.com/auth0/react-native-auth0/pull/1654) ([gabrieldonadel](https://github.com/gabrieldonadel)) + ## [v5.11.0](https://github.com/auth0/react-native-auth0/tree/v5.11.0) (2026-07-31) + [Full Changelog](https://github.com/auth0/react-native-auth0/compare/v5.10.0...v5.11.0) **Added** + - feat: enforce IPSIE session_expiry with a SESSION_EXPIRED error [\#1597](https://github.com/auth0/react-native-auth0/pull/1597) ([subhankarmaiti](https://github.com/subhankarmaiti)) - feat: add passkeys support for web [\#1604](https://github.com/auth0/react-native-auth0/pull/1604) ([NandanPrabhu](https://github.com/NandanPrabhu)) - feat: add My Account API support on the web platform [\#1608](https://github.com/auth0/react-native-auth0/pull/1608) ([subhankarmaiti](https://github.com/subhankarmaiti)) diff --git a/package.json b/package.json index ebcad21a..dcd547a7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-auth0", "title": "React Native Auth0", - "version": "5.11.0", + "version": "5.11.1", "description": "React Native toolkit for Auth0 API", "main": "lib/commonjs/index.js", "module": "lib/module/index.js", diff --git a/src/core/utils/telemetry.ts b/src/core/utils/telemetry.ts index 539f87d5..ecb97747 100644 --- a/src/core/utils/telemetry.ts +++ b/src/core/utils/telemetry.ts @@ -1,6 +1,6 @@ export const telemetry = { name: 'react-native-auth0', - version: '5.11.0', + version: '5.11.1', }; export type Telemetry = {