Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v5.11.0
v5.11.1
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
8 changes: 7 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ def reactNativeArchitectures() {
}

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"
}
apply plugin: "com.facebook.react"

def getExtOrIntegerDefault(name) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/core/utils/telemetry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const telemetry = {
name: 'react-native-auth0',
version: '5.11.0',
version: '5.11.1',
};

export type Telemetry = {
Expand Down
9 changes: 9 additions & 0 deletions src/factory/Auth0ClientFactory.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ 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 Auth0Client; selected by bundlers when targeting web.
* Clients are cached by config signature so remounts reuse the same instance
Expand All @@ -23,6 +26,12 @@ export class Auth0ClientFactory {
static createClient(options: Auth0Options): Auth0Client {
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) {
Expand Down
49 changes: 49 additions & 0 deletions src/factory/__tests__/Auth0ClientFactory.web.spec.ts
Original file line number Diff line number Diff line change
@@ -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));
});
});
44 changes: 44 additions & 0 deletions src/factory/__tests__/Auth0ClientFactory.web.ssr.spec.ts
Original file line number Diff line number Diff line change
@@ -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));
});
});
30 changes: 3 additions & 27 deletions src/platforms/web/adapters/WebAuth0Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,9 @@ export class WebAuth0Client implements Auth0Client {
private readonly httpClient: HttpClient;
private readonly tokenType: TokenType;
public readonly client: SpaAuth0Client;
private static spaClient: SpaAuth0Client | null = null;

private logoutInProgress = false;

/**
* Factory method to get a singleton instance of SpaAuth0Client.
* This ensures that the client is only created once and reused.
*
* @param options - The Auth0ClientOptions to configure the client.
* @returns An instance of SpaAuth0Client.
*/
private static getSpaClient(options: Auth0ClientOptions): SpaAuth0Client {
if (WebAuth0Client.spaClient) {
return WebAuth0Client.spaClient;
}
WebAuth0Client.spaClient = new SpaAuth0Client(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}`;
const useDPoP = options.useDPoP ?? false;
Expand Down Expand Up @@ -99,9 +75,9 @@ export class WebAuth0Client implements Auth0Client {
},
};

// 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 SpaAuth0Client(clientOptions);

// Create a bound getDPoPHeaders function for the orchestrator
const getDPoPHeadersForOrchestrator = async (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ describe('WebAuth0Client - getDPoPHeaders', () => {

beforeEach(() => {
jest.clearAllMocks();
WebAuth0Client.resetSpaClientSingleton();

// Setup window.location mock
Object.defineProperty(window, 'location', {
Expand Down
5 changes: 0 additions & 5 deletions src/platforms/web/adapters/__tests__/WebAuth0Client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,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: {
Expand Down Expand Up @@ -166,9 +163,7 @@ describe('WebAuth0Client', () => {
});

afterEach(() => {
// Clear all mocks and reset singleton
jest.clearAllMocks();
WebAuth0Client.resetSpaClientSingleton();
});

describe('constructor', () => {
Expand Down
Loading