Skip to content
Open
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
55 changes: 55 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [Use with a Class Component](#use-with-a-class-component)
- [Protect a Route](#protect-a-route)
- [Call an API](#call-an-api)
- [Use Auth0 outside of React](#use-auth0-outside-of-react)
- [Protecting a route in a `react-router-dom v6` app](#protecting-a-route-in-a-react-router-dom-v6-app)
- [Protecting a route in a Gatsby app](#protecting-a-route-in-a-gatsby-app)
- [Protecting a route in a Next.js app (in SPA mode)](#protecting-a-route-in-a-nextjs-app-in-spa-mode)
Expand Down Expand Up @@ -102,6 +103,60 @@ const Posts = () => {
export default Posts;
```

## Use Auth0 outside of React

If you need to share an `Auth0Client` instance between the React tree and code that has no access to React's lifecycle — such as TanStack Start client function middleware — use `createAuth0Client` to create a shared instance and pass it to `Auth0Provider` via the `client` prop.

Using `createAuth0Client` ensures the `auth0-react` telemetry header is set correctly on the client.

```jsx
// auth0-client.js
import { createAuth0Client } from '@auth0/auth0-react';

export const auth0Client = createAuth0Client({
domain: 'YOUR_AUTH0_DOMAIN',
clientId: 'YOUR_AUTH0_CLIENT_ID',
authorizationParams: {
redirect_uri: window.location.origin,
},
});
```

Pass the client to `Auth0Provider`:

```jsx
import { Auth0Provider } from '@auth0/auth0-react';
import { auth0Client } from './auth0-client';

export default function App() {
return (
<Auth0Provider client={auth0Client}>
<MyApp />
</Auth0Provider>
);
}
```

> **Note:** `getTokenSilently()` requires an active session. Ensure `Auth0Provider` has mounted and completed initialization before calling it outside React.

Use the same client instance in a TanStack Start client function middleware:

```js
import { createMiddleware } from '@tanstack/react-start';
import { auth0Client } from './auth0-client';

export const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const token = await auth0Client.getTokenSilently();
return next({
headers: {
Authorization: `Bearer ${token}`,
},
});
},
);
```

## Custom token exchange

Exchange an external subject token for Auth0 tokens using the token exchange flow (RFC 8693):
Expand Down
23 changes: 22 additions & 1 deletion __tests__/auth-provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import '@testing-library/jest-dom';
import { act, render, renderHook, screen, waitFor } from '@testing-library/react';
import React, { StrictMode, useContext } from 'react';
import pkg from '../package.json';
import { Auth0Provider, useAuth0 } from '../src';
import { Auth0Provider, createAuth0Client, useAuth0 } from '../src';
import Auth0Context, {
Auth0ContextInterface,
initialContext,
Expand Down Expand Up @@ -134,6 +134,27 @@ describe('Auth0Provider', () => {
});
});

it('should use provided client instance without creating a new one', async () => {
const wrapper = createWrapper({ client: clientMock });
renderHook(() => useContext(Auth0Context), { wrapper });
await waitFor(() => {
expect(Auth0Client).not.toHaveBeenCalled();
expect(clientMock.checkSession).toHaveBeenCalled();
});
});

it('should inject auth0-react telemetry when using createAuth0Client', async () => {
createAuth0Client({ clientId: 'foo', domain: 'bar' });
expect(Auth0Client).toHaveBeenCalledWith(
expect.objectContaining({
auth0Client: {
name: 'auth0-react',
version: pkg.version,
},
})
);
});

it('should check session when logged out', async () => {
const wrapper = createWrapper();
const { result } = renderHook(
Expand Down
12 changes: 6 additions & 6 deletions __tests__/helpers.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React, { PropsWithChildren } from 'react';
import Auth0Provider, { Auth0ProviderOptions } from '../src/auth0-provider';

export const createWrapper = ({
clientId = '__test_client_id__',
domain = '__test_domain__',
...opts
}: Partial<Auth0ProviderOptions> = {}) => {
export const createWrapper = (opts: Partial<Auth0ProviderOptions> = {}) => {
const providerProps =
'client' in opts && opts.client != null
? (opts as Auth0ProviderOptions)
: ({ clientId: '__test_client_id__', domain: '__test_domain__', ...opts } as Auth0ProviderOptions);
return function Wrapper({
children,
}: PropsWithChildren<Record<string, unknown>>): React.JSX.Element {
return (
<Auth0Provider domain={domain} clientId={clientId} {...opts}>
<Auth0Provider {...providerProps}>
{children}
</Auth0Provider>
);
Expand Down
4 changes: 2 additions & 2 deletions examples/cra-react-router/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import React, { PropsWithChildren } from 'react';
import App from './App';
import { Auth0Provider, AppState, Auth0ContextInterface, User } from '@auth0/auth0-react';
import { BrowserRouter, useNavigate } from 'react-router-dom';
import { Auth0ProviderOptions } from '../../../src/index.js';
import { Auth0ProviderWithConfigOptions } from '../../../src/index.js';

const Auth0ProviderWithRedirectCallback = ({
children,
context,
...props
}: PropsWithChildren<Omit<Auth0ProviderOptions, 'context'>> & {
}: PropsWithChildren<Omit<Auth0ProviderWithConfigOptions, 'context'>> & {
context?: React.Context<Auth0ContextInterface<User>>
}) => {
const navigate = useNavigate();
Expand Down
61 changes: 53 additions & 8 deletions src/auth0-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ export type AppState = {
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};

/**
* The main configuration to instantiate the `Auth0Provider`.
*/
export interface Auth0ProviderOptions<TUser extends User = User> extends Auth0ClientOptions {
type Auth0ProviderBaseOptions<TUser extends User = User> = {
/**
* The child nodes your Provider has wrapped
*/
Expand Down Expand Up @@ -97,7 +94,31 @@ export interface Auth0ProviderOptions<TUser extends User = User> extends Auth0Cl
* For a sample on using multiple Auth0Providers review the [React Account Linking Sample](https://github.com/auth0-samples/auth0-link-accounts-sample/tree/react-variant)
*/
context?: React.Context<Auth0ContextInterface<TUser>>;
}
};

/**
* Options for `Auth0Provider` when configuring Auth0 via `domain` and `clientId`.
* Use this type when building wrapper components around `Auth0Provider`.
*/
export type Auth0ProviderWithConfigOptions<TUser extends User = User> =
Auth0ProviderBaseOptions<TUser> & Auth0ClientOptions & { client?: never };

/**
* Options for `Auth0Provider` when supplying a pre-configured `Auth0Client` instance.
* Use `createAuth0Client` to create the client so telemetry is set correctly.
*/
export type Auth0ProviderWithClientOptions<TUser extends User = User> =
Auth0ProviderBaseOptions<TUser> & { client: Auth0Client };

/**
* The main configuration to instantiate the `Auth0Provider`.
*
* Either provide `domain` and `clientId` (`Auth0ProviderWithConfigOptions`)
* or a pre-configured `client` instance (`Auth0ProviderWithClientOptions`).
*/
export type Auth0ProviderOptions<TUser extends User = User> =
| Auth0ProviderWithConfigOptions<TUser>
| Auth0ProviderWithClientOptions<TUser>;

/**
* Replaced by the package version at build time.
Expand All @@ -109,7 +130,7 @@ declare const __VERSION__: string;
* @ignore
*/
const toAuth0ClientOptions = (
opts: Auth0ProviderOptions
opts: Auth0ClientOptions
): Auth0ClientOptions => {
deprecateRedirectUri(opts);

Expand All @@ -122,6 +143,29 @@ const toAuth0ClientOptions = (
};
};

/**
* Creates a new `Auth0Client` with the `auth0-react` SDK telemetry header set.
*
* Use this when you need to share a single client instance with `Auth0Provider`
* and also access Auth0 outside of React (e.g. in TanStack Start client function middleware).
*
* @example
* ```tsx
* const client = createAuth0Client({ domain, clientId });
*
* // Use outside React (e.g. in TanStack Start client function middleware)
* const token = await client.getTokenSilently();
*
* // Use inside React
* function App() {
* return <Auth0Provider client={client}><MyApp /></Auth0Provider>;
* }
* ```
*/
export const createAuth0Client = (options: Auth0ClientOptions): Auth0Client => {
return new Auth0Client(toAuth0ClientOptions(options));
};

/**
* @ignore
*/
Expand Down Expand Up @@ -151,10 +195,11 @@ const Auth0Provider = <TUser extends User = User>(opts: Auth0ProviderOptions<TUs
skipRedirectCallback,
onRedirectCallback = defaultOnRedirectCallback,
context = Auth0Context,
client: providedClient,
...clientOpts
} = opts;
} = opts as Auth0ProviderBaseOptions<TUser> & Auth0ClientOptions & { client?: Auth0Client };
const [client] = useState(
() => new Auth0Client(toAuth0ClientOptions(clientOpts))
() => providedClient ?? new Auth0Client(toAuth0ClientOptions(clientOpts))
);
const [state, dispatch] = useReducer(reducer<TUser>, initialAuthState as AuthState<TUser>);
const didInitialise = useRef(false);
Expand Down
6 changes: 5 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
export {
default as Auth0Provider,
Auth0ProviderOptions,
Auth0ProviderWithConfigOptions,
Auth0ProviderWithClientOptions,
AppState,
ConnectedAccount
ConnectedAccount,
createAuth0Client,
} from './auth0-provider';
export { default as useAuth0 } from './use-auth0';
export { default as withAuth0, WithAuth0Props } from './with-auth0';
Expand All @@ -18,6 +21,7 @@ export {
RedirectLoginOptions,
} from './auth0-context';
export {
Auth0Client,
AuthorizationParams,
PopupLoginOptions,
PopupConfigOptions,
Expand Down
Loading