From f32b73302128ffc47edffe2c115d1390f8de008d Mon Sep 17 00:00:00 2001 From: Gyanesh Gouraw Date: Thu, 3 Sep 2026 01:04:13 +0530 Subject: [PATCH 1/6] feat: add useEnterpriseConnect hook for Enterprise Connect Adds useEnterpriseConnect hook wrapping isFederatedDomain (WebFinger domain discovery) and loginWithRedirect (with login_hint set). Exports UseEnterpriseConnect type from the package root. Adds module augmentation for the unreleased isFederatedDomain export in @auth0/auth0-spa-js. Co-Authored-By: Claude Sonnet 4.6 --- __mocks__/@auth0/auth0-spa-js.tsx | 2 + __tests__/use-enterprise-connect.test.tsx | 86 +++++++++++++++++++++++ src/auth0-spa-js-augment.d.ts | 15 ++++ src/index.tsx | 4 ++ src/use-enterprise-connect.tsx | 74 +++++++++++++++++++ 5 files changed, 181 insertions(+) create mode 100644 __tests__/use-enterprise-connect.test.tsx create mode 100644 src/auth0-spa-js-augment.d.ts create mode 100644 src/use-enterprise-connect.tsx diff --git a/__mocks__/@auth0/auth0-spa-js.tsx b/__mocks__/@auth0/auth0-spa-js.tsx index 45f4174c..b1743577 100644 --- a/__mocks__/@auth0/auth0-spa-js.tsx +++ b/__mocks__/@auth0/auth0-spa-js.tsx @@ -103,3 +103,5 @@ export const PasskeyRegisterError = actual.PasskeyRegisterError; export const PasskeyChallengeError = actual.PasskeyChallengeError; export const PasskeyGetTokenError = actual.PasskeyGetTokenError; export const MyAccountApiError = actual.MyAccountApiError; + +export const isFederatedDomain = jest.fn(); diff --git a/__tests__/use-enterprise-connect.test.tsx b/__tests__/use-enterprise-connect.test.tsx new file mode 100644 index 00000000..33f9d834 --- /dev/null +++ b/__tests__/use-enterprise-connect.test.tsx @@ -0,0 +1,86 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { + isFederatedDomain as spaIsFederatedDomain, + Auth0Client, +} from '@auth0/auth0-spa-js'; +import useEnterpriseConnect from '../src/use-enterprise-connect'; +import { createWrapper } from './helpers'; + +jest.mock('@auth0/auth0-spa-js'); + +const clientMock = jest.mocked(new Auth0Client({ clientId: '', domain: '' })); +const federatedMock = jest.mocked(spaIsFederatedDomain); + +describe('useEnterpriseConnect', () => { + beforeEach(() => { + jest.clearAllMocks(); + clientMock.getConfiguration.mockReturnValue({ + domain: '__test_domain__', + clientId: '__test_client_id__', + }); + }); + + it('calls isFederatedDomain with the configured domain and email domain', async () => { + federatedMock.mockResolvedValueOnce(true); + const wrapper = createWrapper(); + const { result } = renderHook(() => useEnterpriseConnect(), { wrapper }); + + const federated = await result.current.isFederatedDomain('acme.com'); + + expect(federatedMock).toHaveBeenCalledWith( + '__test_domain__', + 'acme.com', + undefined + ); + expect(federated).toBe(true); + }); + + it('forwards options to isFederatedDomain', async () => { + federatedMock.mockResolvedValueOnce(false); + const customFetch = jest.fn(); + const wrapper = createWrapper(); + const { result } = renderHook(() => useEnterpriseConnect(), { wrapper }); + + await result.current.isFederatedDomain('acme.com', { customFetch }); + + expect(federatedMock).toHaveBeenCalledWith('__test_domain__', 'acme.com', { + customFetch, + }); + }); + + it('loginWithSSO calls loginWithRedirect with login_hint set from the email', async () => { + const wrapper = createWrapper(); + const { result } = renderHook(() => useEnterpriseConnect(), { wrapper }); + await waitFor(() => + expect(clientMock.loginWithRedirect).not.toBeNull() + ); + + await result.current.loginWithSSO('jane@acme.com'); + + expect(clientMock.loginWithRedirect).toHaveBeenCalledWith({ + authorizationParams: { login_hint: 'jane@acme.com' }, + }); + }); + + it('loginWithSSO preserves caller authorizationParams and other options', async () => { + const wrapper = createWrapper(); + const { result } = renderHook(() => useEnterpriseConnect(), { wrapper }); + + await result.current.loginWithSSO('jane@acme.com', { + authorizationParams: { + connection: 'okta', + organization: 'org_123', + }, + appState: { returnTo: '/dashboard' }, + }); + + expect(clientMock.loginWithRedirect).toHaveBeenCalledWith({ + appState: { returnTo: '/dashboard' }, + authorizationParams: { + connection: 'okta', + organization: 'org_123', + login_hint: 'jane@acme.com', + }, + }); + }); +}); diff --git a/src/auth0-spa-js-augment.d.ts b/src/auth0-spa-js-augment.d.ts new file mode 100644 index 00000000..abbdee71 --- /dev/null +++ b/src/auth0-spa-js-augment.d.ts @@ -0,0 +1,15 @@ +// Augments @auth0/auth0-spa-js with unreleased Enterprise Connect exports. +// Remove once isFederatedDomain ships in the published package. +import '@auth0/auth0-spa-js'; + +declare module '@auth0/auth0-spa-js' { + export interface IsFederatedDomainOptions { + customFetch?: typeof fetch; + telemetry?: { name: string; version: string; env?: Record }; + } + export function isFederatedDomain( + auth0Domain: string, + emailDomain: string, + options?: IsFederatedDomainOptions + ): Promise; +} diff --git a/src/index.tsx b/src/index.tsx index 5c167feb..9b283aa1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,6 +11,10 @@ export { default as useAuth0Suspense, Auth0SuspenseContextInterface, } from './use-auth0-suspense'; +export { + default as useEnterpriseConnect, + UseEnterpriseConnect, +} from './use-enterprise-connect'; export { default as withAuth0, WithAuth0Props } from './with-auth0'; export { default as withAuthenticationRequired, diff --git a/src/use-enterprise-connect.tsx b/src/use-enterprise-connect.tsx new file mode 100644 index 00000000..0a4f273a --- /dev/null +++ b/src/use-enterprise-connect.tsx @@ -0,0 +1,74 @@ +import { useCallback, useContext } from 'react'; +import { + isFederatedDomain as spaIsFederatedDomain, + IsFederatedDomainOptions, +} from '@auth0/auth0-spa-js'; +import Auth0Context, { + Auth0ContextInterface, + RedirectLoginOptions, +} from './auth0-context'; + +/** + * The shape returned by the `useEnterpriseConnect` hook. + */ +export interface UseEnterpriseConnect { + /** + * Runs WebFinger domain discovery for the given email domain against the + * Auth0 domain configured on the `Auth0Provider`. Returns `true` only if + * the domain is managed by Auth0 for enterprise SSO. A routing hint, not a + * security control: it returns `false` on any failure. + */ + isFederatedDomain: ( + emailDomain: string, + options?: IsFederatedDomainOptions + ) => Promise; + /** + * Starts the enterprise SSO redirect, passing the email as `login_hint` + * so Home Realm Discovery can resolve the connection and organization. Any + * `authorizationParams` supplied by the caller are preserved. + */ + loginWithSSO: ( + email: string, + options?: RedirectLoginOptions + ) => Promise; +} + +/** + * ```js + * const { isFederatedDomain, loginWithSSO } = useEnterpriseConnect(); + * ``` + * + * Convenience hook for the Enterprise Connect flow. `isFederatedDomain` reads + * the Auth0 domain from the `Auth0Provider` configuration, so callers pass + * only the email domain. `loginWithSSO` is sugar over `loginWithRedirect` + * that sets `login_hint` to the provided email. + */ +const useEnterpriseConnect = ( + context = Auth0Context +): UseEnterpriseConnect => { + const { getConfiguration, loginWithRedirect } = useContext( + context + ) as Auth0ContextInterface; + + const isFederatedDomain = useCallback( + (emailDomain: string, options?: IsFederatedDomainOptions) => + spaIsFederatedDomain(getConfiguration().domain, emailDomain, options), + [getConfiguration] + ); + + const loginWithSSO = useCallback( + (email: string, options?: RedirectLoginOptions) => + loginWithRedirect({ + ...options, + authorizationParams: { + ...options?.authorizationParams, + login_hint: email, + }, + }), + [loginWithRedirect] + ); + + return { isFederatedDomain, loginWithSSO }; +}; + +export default useEnterpriseConnect; From 355ba3364418fc199bddbac458304e61209a9d89 Mon Sep 17 00:00:00 2001 From: Gyanesh Gouraw Date: Thu, 3 Sep 2026 01:13:26 +0530 Subject: [PATCH 2/6] fix: move spa-js augment out of src/, fix telemetry shape Move auth0-spa-js-augment.d.ts from src/ to type-augments/ so it is never swept into dist/ declaration emit. Wire it via tsconfig "files" so tsc picks it up for type-checking without emitting it. Fix telemetry type to match the real TelemetryConfig discriminated union (remove env field, use enabled/name/version union). Add removal-trigger reference. Co-Authored-By: Claude Sonnet 4.6 --- src/auth0-spa-js-augment.d.ts | 15 --------------- tsconfig.json | 3 ++- type-augments/auth0-spa-js-augment.d.ts | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 16 deletions(-) delete mode 100644 src/auth0-spa-js-augment.d.ts create mode 100644 type-augments/auth0-spa-js-augment.d.ts diff --git a/src/auth0-spa-js-augment.d.ts b/src/auth0-spa-js-augment.d.ts deleted file mode 100644 index abbdee71..00000000 --- a/src/auth0-spa-js-augment.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Augments @auth0/auth0-spa-js with unreleased Enterprise Connect exports. -// Remove once isFederatedDomain ships in the published package. -import '@auth0/auth0-spa-js'; - -declare module '@auth0/auth0-spa-js' { - export interface IsFederatedDomainOptions { - customFetch?: typeof fetch; - telemetry?: { name: string; version: string; env?: Record }; - } - export function isFederatedDomain( - auth0Domain: string, - emailDomain: string, - options?: IsFederatedDomainOptions - ): Promise; -} diff --git a/tsconfig.json b/tsconfig.json index 49d69d83..529d614a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,5 +24,6 @@ "noUncheckedIndexedAccess": true, "verbatimModuleSyntax": false }, - "include": ["src"] + "include": ["src"], + "files": ["type-augments/auth0-spa-js-augment.d.ts"] } \ No newline at end of file diff --git a/type-augments/auth0-spa-js-augment.d.ts b/type-augments/auth0-spa-js-augment.d.ts new file mode 100644 index 00000000..8ea7f508 --- /dev/null +++ b/type-augments/auth0-spa-js-augment.d.ts @@ -0,0 +1,20 @@ +// Augments @auth0/auth0-spa-js with the unreleased Enterprise Connect exports +// so auth0-react compiles against a locally-mapped spa-js build. +// Remove once `isFederatedDomain` ships in a published @auth0/auth0-spa-js +// release and the dependency is bumped (see the enterprise-connect spa-js +// release). Kept outside src/ so it is never emitted into dist/ types. +import '@auth0/auth0-spa-js'; + +declare module '@auth0/auth0-spa-js' { + export interface IsFederatedDomainOptions { + customFetch?: typeof fetch; + telemetry?: + | { enabled: false } + | ({ enabled?: true } & { name: string; version: string }); + } + export function isFederatedDomain( + auth0Domain: string, + emailDomain: string, + options?: IsFederatedDomainOptions + ): Promise; +} From dbe37585ab3661a03638b6be4cf70eba5a577041 Mon Sep 17 00:00:00 2001 From: Gyanesh Gouraw Date: Thu, 3 Sep 2026 02:21:30 +0530 Subject: [PATCH 3/6] feat: add Enterprise Connect examples and remove local type augment --- EXAMPLES.md | 86 ++++++++++++++++++++++++- tsconfig.json | 3 +- type-augments/auth0-spa-js-augment.d.ts | 20 ------ 3 files changed, 86 insertions(+), 23 deletions(-) delete mode 100644 type-augments/auth0-spa-js-augment.d.ts diff --git a/EXAMPLES.md b/EXAMPLES.md index 462ba23b..5e25ae33 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1984,4 +1984,88 @@ If initialization fails and the user subsequently signs in by some other means for example a `loginWithPopup` triggered from outside the boundary — the SDK re-checks the session once. If that check succeeds, retrying your Error Boundary renders the subtree normally; if it fails again, the boundary keeps showing the -error. \ No newline at end of file +error. + +## Enterprise Connect + +> Enterprise Connect is an Early Access feature. Confirm the tenant-side +> requirements with your Auth0 contact. + +Enterprise Connect layers enterprise SSO on top of your own auth server. The +`useEnterpriseConnect` hook exposes `isFederatedDomain` (WebFinger domain +discovery against your configured Auth0 domain) and `loginWithSSO` (a +`loginWithRedirect` that sets `login_hint`). + +Login form: discover the domain, then route to SSO or your own login. + +```jsx +import { useEnterpriseConnect } from '@auth0/auth0-react'; + +export function LoginForm() { + const { isFederatedDomain, loginWithSSO } = useEnterpriseConnect(); + + const handleSubmit = async (event) => { + event.preventDefault(); + const email = event.target.email.value; + const emailDomain = email.split('@')[1]; + + if (await isFederatedDomain(emailDomain)) { + await loginWithSSO(email, { + appState: { returnTo: window.location.pathname }, + }); + } else { + // your existing login flow + showPasswordForm(email); + } + }; + + return ( +
+ + +
+ ); +} +``` + +Callback route: complete the login, validate the organization, then read the +enriched claims. + +```jsx +import { useAuth0 } from '@auth0/auth0-react'; +import { useEffect, useRef } from 'react'; + +const ALLOWED_ORGS = ['org_123']; + +export function Callback() { + const { handleRedirectCallback, getIdTokenClaims, logout } = useAuth0(); + const handled = useRef(false); + + useEffect(() => { + if (handled.current) return; + handled.current = true; + + (async () => { + await handleRedirectCallback(); + const claims = await getIdTokenClaims(); + + if (!claims?.org_id || !ALLOWED_ORGS.includes(claims.org_id)) { + await logout({ logoutParams: { returnTo: window.location.origin } }); + return; + } + + console.log('Logged in as', claims.email, 'in org', claims.org_id); + })(); + }, [handleRedirectCallback, getIdTokenClaims, logout]); + + return

Completing login...

; +} +``` + +Logout must be federated to end the enterprise IdP session: + +```jsx +await logout({ + logoutParams: { federated: true, returnTo: window.location.origin }, +}); +``` \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 529d614a..49d69d83 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,5 @@ "noUncheckedIndexedAccess": true, "verbatimModuleSyntax": false }, - "include": ["src"], - "files": ["type-augments/auth0-spa-js-augment.d.ts"] + "include": ["src"] } \ No newline at end of file diff --git a/type-augments/auth0-spa-js-augment.d.ts b/type-augments/auth0-spa-js-augment.d.ts deleted file mode 100644 index 8ea7f508..00000000 --- a/type-augments/auth0-spa-js-augment.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Augments @auth0/auth0-spa-js with the unreleased Enterprise Connect exports -// so auth0-react compiles against a locally-mapped spa-js build. -// Remove once `isFederatedDomain` ships in a published @auth0/auth0-spa-js -// release and the dependency is bumped (see the enterprise-connect spa-js -// release). Kept outside src/ so it is never emitted into dist/ types. -import '@auth0/auth0-spa-js'; - -declare module '@auth0/auth0-spa-js' { - export interface IsFederatedDomainOptions { - customFetch?: typeof fetch; - telemetry?: - | { enabled: false } - | ({ enabled?: true } & { name: string; version: string }); - } - export function isFederatedDomain( - auth0Domain: string, - emailDomain: string, - options?: IsFederatedDomainOptions - ): Promise; -} From 96c7a743687a1a477c70e051e008679da93aee4a Mon Sep 17 00:00:00 2001 From: Gyanesh Gouraw Date: Tue, 15 Sep 2026 23:36:17 +0530 Subject: [PATCH 4/6] feat: add support for enterpriseConnect flag in Auth0Provider and update related documentation --- EXAMPLES.md | 47 ++++++++++++++++++++++++++------ __tests__/auth-provider.test.tsx | 21 ++++++++++++++ package-lock.json | 33 ++++++++++------------ package.json | 2 +- src/index.tsx | 1 + src/use-enterprise-connect.tsx | 1 + 6 files changed, 77 insertions(+), 28 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 5e25ae33..230aa0f3 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1996,6 +1996,23 @@ Enterprise Connect layers enterprise SSO on top of your own auth server. The discovery against your configured Auth0 domain) and `loginWithSSO` (a `loginWithRedirect` that sets `login_hint`). +Set `enterpriseConnect` on `Auth0Provider` to signal that the app runs in +Enterprise Connect mode. The flag is forwarded to the underlying +`@auth0/auth0-spa-js` client, which uses it to warn at initialisation when the +configuration contradicts Enterprise Connect (for example `useRefreshTokens`, +`offline_access` in `scope`, or a static `organization`). + +```jsx + + + +``` + Login form: discover the domain, then route to SSO or your own login. ```jsx @@ -2014,8 +2031,8 @@ export function LoginForm() { appState: { returnTo: window.location.pathname }, }); } else { - // your existing login flow - showPasswordForm(email); + // Not a federated domain: hand off to your existing login flow + // (e.g. render your password form). Replace with your own routing. } }; @@ -2028,13 +2045,19 @@ export function LoginForm() { } ``` -Callback route: complete the login, validate the organization, then read the -enriched claims. +Callback route: complete the login, then read the enriched claims. + +Validating the `org_id` claim is an **optional** application-level +authorization step, not an SDK requirement. Add it only if your app restricts +access to specific organizations. A federated user on a connection that +predates `org_id` claims will not have one, so treat a missing `org_id` as "not +org-scoped" rather than an automatic failure. ```jsx import { useAuth0 } from '@auth0/auth0-react'; import { useEffect, useRef } from 'react'; +// Optional: only if your app restricts access to specific organizations. const ALLOWED_ORGS = ['org_123']; export function Callback() { @@ -2049,12 +2072,17 @@ export function Callback() { await handleRedirectCallback(); const claims = await getIdTokenClaims(); - if (!claims?.org_id || !ALLOWED_ORGS.includes(claims.org_id)) { - await logout({ logoutParams: { returnTo: window.location.origin } }); + // Optional org check. Remove this block if you do not gate on org. + if (claims?.org_id && !ALLOWED_ORGS.includes(claims.org_id)) { + // Federated logout ends the enterprise IdP session too, so the next + // login runs email discovery again instead of silently re-using SSO. + await logout({ + logoutParams: { federated: true, returnTo: window.location.origin }, + }); return; } - console.log('Logged in as', claims.email, 'in org', claims.org_id); + console.log('Logged in as', claims?.email, 'in org', claims?.org_id); })(); }, [handleRedirectCallback, getIdTokenClaims, logout]); @@ -2068,4 +2096,7 @@ Logout must be federated to end the enterprise IdP session: await logout({ logoutParams: { federated: true, returnTo: window.location.origin }, }); -``` \ No newline at end of file +``` + +The `returnTo` URL must be registered in the application's **Allowed Logout +URLs** in the Auth0 Dashboard, or the logout redirect will be rejected. \ No newline at end of file diff --git a/__tests__/auth-provider.test.tsx b/__tests__/auth-provider.test.tsx index 8edc5ce7..d6775f63 100644 --- a/__tests__/auth-provider.test.tsx +++ b/__tests__/auth-provider.test.tsx @@ -89,6 +89,27 @@ describe('Auth0Provider', () => { }); }); + it('should forward the enterpriseConnect flag to Auth0Client', async () => { + const opts = { + clientId: 'foo', + domain: 'bar', + enterpriseConnect: true, + }; + const wrapper = createWrapper(opts); + renderHook(() => useContext(Auth0Context), { + wrapper, + }); + await waitFor(() => { + expect(Auth0Client).toHaveBeenCalledWith( + expect.objectContaining({ + clientId: 'foo', + domain: 'bar', + enterpriseConnect: true, + }) + ); + }); + }); + it('should support redirectUri', async () => { const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined); const opts = { diff --git a/package-lock.json b/package-lock.json index f0d16caf..c4e8d63f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.25.0", "license": "MIT", "dependencies": { - "@auth0/auth0-spa-js": "^2.25.0" + "@auth0/auth0-spa-js": "^2.26.0" }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.1", @@ -80,20 +80,18 @@ "license": "ISC" }, "node_modules/@auth0/auth0-auth-js": { - "version": "1.10.0", - "integrity": "sha512-FDzc0lbaG1E++4sjx3zW7BB9gVK2ODLmuPStGxYPeclKwLMFKkmVyWR8bCyQVNXvTS1KCHeX0dC9HMTJ0WBCJg==", - "license": "MIT", + "version": "1.15.0", + "integrity": "sha512-obn/Qhxx3eNs2wV6/lLsIDIcEiSJvBEEy15hppB5Me1j2No2toa7KqQcXHBCyGX4mBBdVnxZJBjEX6E4WanYwg==", "dependencies": { "jose": "^6.0.8", "openid-client": "^6.8.0" } }, "node_modules/@auth0/auth0-spa-js": { - "version": "2.25.0", - "integrity": "sha512-ISqLDRQcxFfF678AgtYDCdXESHX9W8II339xj/F6ujIptjK/ZkcTwq4HJs3RG7F24xRq1Xx1Vsc2uSCpyCMcUA==", - "license": "MIT", + "version": "2.26.0", + "integrity": "sha512-pv2Si9bJioQqTgk/doh0nIKgnpF1TEW/CvPmC1eYUgwPk/cUIwIzNSPShFBM4G2MnR+pbeD2HhFH6ChJAzIeqg==", "dependencies": { - "@auth0/auth0-auth-js": "^1.10.0", + "@auth0/auth0-auth-js": "^1.15.0", "browser-tabs-lock": "^1.3.0", "dpop": "^2.1.1", "es-cookie": "~1.3.2" @@ -7812,9 +7810,8 @@ } }, "node_modules/jose": { - "version": "6.2.3", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "license": "MIT", + "version": "6.2.12", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -9089,9 +9086,8 @@ "license": "MIT" }, "node_modules/oauth4webapi": { - "version": "3.8.6", - "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", - "license": "MIT", + "version": "3.8.8", + "integrity": "sha512-8N28E+a/oxfXWBgOMt+ZP/JUf/XR+IFbvkAEPP3gznXOMv9BpAAwiIj0TFNz3tGTPc0ZQ8zmWBNgN1nAys0gng==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -9290,12 +9286,11 @@ "dev": true }, "node_modules/openid-client": { - "version": "6.8.4", - "integrity": "sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==", - "license": "MIT", + "version": "6.8.8", + "integrity": "sha512-ZsucJA5Ad04Uv7YN4ql+s4GXNmb9uAYQwyJTsJx7CH/MX3JZioLE2pVKi1SC+YrbSLA4Px3Gi30dMjgOZtb6pA==", "dependencies": { - "jose": "^6.2.2", - "oauth4webapi": "^3.8.5" + "jose": "^6.2.12", + "oauth4webapi": "^3.8.8" }, "funding": { "url": "https://github.com/sponsors/panva" diff --git a/package.json b/package.json index 738e3e0d..fc22a05b 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,6 @@ "react-dom": "^16.11.0 || ^17 || ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" }, "dependencies": { - "@auth0/auth0-spa-js": "^2.25.0" + "@auth0/auth0-spa-js": "^2.26.0" } } diff --git a/src/index.tsx b/src/index.tsx index 9b283aa1..ab92f3ac 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -80,6 +80,7 @@ export type { RevokeRefreshTokenOptions, FetcherConfig, InteractiveErrorHandler, + IsFederatedDomainOptions, // MFA Types MfaApiClient, Authenticator, diff --git a/src/use-enterprise-connect.tsx b/src/use-enterprise-connect.tsx index 0a4f273a..4b0e2496 100644 --- a/src/use-enterprise-connect.tsx +++ b/src/use-enterprise-connect.tsx @@ -51,6 +51,7 @@ const useEnterpriseConnect = ( ) as Auth0ContextInterface; const isFederatedDomain = useCallback( + // domain is read at call time, not when the callback is memoized (emailDomain: string, options?: IsFederatedDomainOptions) => spaIsFederatedDomain(getConfiguration().domain, emailDomain, options), [getConfiguration] From 7bf769202cec78e63674c79cb327358e908d1cdd Mon Sep 17 00:00:00 2001 From: Gyanesh Gouraw Date: Wed, 16 Sep 2026 18:38:29 +0530 Subject: [PATCH 5/6] feat: update examples and documentation for Enterprise Connect integration --- EXAMPLES.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 230aa0f3..e3b18de4 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -23,6 +23,7 @@ - [MyAccount API](#myaccount-api) - [Session Expiry from Upstream IdP (IPSIE)](#session-expiry-from-upstream-idp-ipsie) - [Use Suspense for loading state (React 19+)](#use-suspense-for-loading-state-react-19) +- [Enterprise Connect](#enterprise-connect) ## Use with a Class Component @@ -1996,11 +1997,22 @@ Enterprise Connect layers enterprise SSO on top of your own auth server. The discovery against your configured Auth0 domain) and `loginWithSSO` (a `loginWithRedirect` that sets `login_hint`). -Set `enterpriseConnect` on `Auth0Provider` to signal that the app runs in -Enterprise Connect mode. The flag is forwarded to the underlying -`@auth0/auth0-spa-js` client, which uses it to warn at initialisation when the -configuration contradicts Enterprise Connect (for example `useRefreshTokens`, -`offline_access` in `scope`, or a static `organization`). +`isFederatedDomain` makes a browser-direct cross-origin WebFinger request to +your Auth0 tenant domain. Configure `domain` to the tenant domain +(`YOUR_TENANT.auth0.com` or a custom domain with the WebFinger route enabled), +not a custom domain that omits it: requests to the wrong host will fail and +`isFederatedDomain` will return `false`. + +Set `enterpriseConnect={true}` to put the SDK into this mode. + +Enterprise Connect issues no refresh token, so the access token expires at the +configured token lifetime with no silent renewal. Plan to re-authenticate the +user through the login flow when the token expires; `getAccessTokenSilently` +will not refresh it. + +Treat Enterprise Connect as identity only: extract the ID token claims after +login and issue your own application session or API tokens from them. Do not +rely on the Auth0 access token for long-lived API authorization. ```jsx Date: Wed, 16 Sep 2026 19:10:44 +0530 Subject: [PATCH 6/6] feat: add useEnterpriseConnect link to README and update Callback function in examples --- EXAMPLES.md | 24 ++++++++++++------------ README.md | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index e3b18de4..20992283 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -2057,7 +2057,9 @@ export function LoginForm() { } ``` -Callback route: complete the login, then read the enriched claims. +After the SSO redirect, `Auth0Provider` completes the token exchange +automatically. Read the settled auth state in your `App` component to validate +the session and navigate. Validating the `org_id` claim is an **optional** application-level authorization step, not an SDK requirement. Add it only if your app restricts @@ -2067,21 +2069,18 @@ org-scoped" rather than an automatic failure. ```jsx import { useAuth0 } from '@auth0/auth0-react'; -import { useEffect, useRef } from 'react'; +import { useEffect } from 'react'; // Optional: only if your app restricts access to specific organizations. const ALLOWED_ORGS = ['org_123']; -export function Callback() { - const { handleRedirectCallback, getIdTokenClaims, logout } = useAuth0(); - const handled = useRef(false); +export function App() { + const { isLoading, isAuthenticated, getIdTokenClaims, logout } = useAuth0(); useEffect(() => { - if (handled.current) return; - handled.current = true; + if (isLoading || !isAuthenticated) return; (async () => { - await handleRedirectCallback(); const claims = await getIdTokenClaims(); // Optional org check. Remove this block if you do not gate on org. @@ -2094,12 +2093,13 @@ export function Callback() { return; } - // Login complete. Navigate to your app's post-login destination. - // e.g. window.location.replace(appState?.returnTo ?? '/'); + // Session valid. Render your app or navigate to the post-login destination. })(); - }, [handleRedirectCallback, getIdTokenClaims, logout]); + }, [isLoading, isAuthenticated, getIdTokenClaims, logout]); + + if (isLoading) return

Loading...

; - return

Completing login...

; + return
{/* your app */}
; } ``` diff --git a/README.md b/README.md index 2b13abe9..09b3a7c5 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ Explore public API's available in auth0-react. - [useAuth0Suspense](https://auth0.github.io/auth0-react/functions/useAuth0Suspense.html) - [withAuth0](https://auth0.github.io/auth0-react/functions/withAuth0.html) - [withAuthenticationRequired](https://auth0.github.io/auth0-react/functions/withAuthenticationRequired.html) +- [useEnterpriseConnect](https://auth0.github.io/auth0-react/functions/useEnterpriseConnect.html) ## Feedback