Skip to content
Merged
70 changes: 69 additions & 1 deletion EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- [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)
- [Forcing an Experiment Center variant](#forcing-an-experiment-center-variant)

## Use with a Class Component

Expand Down Expand Up @@ -2112,4 +2113,71 @@ await logout({
```

The `returnTo` URL must be registered in the application's **Allowed Logout
URLs** in the Auth0 Dashboard, or the logout redirect will be rejected.
URLs** in the Auth0 Dashboard, or the logout redirect will be rejected.

## Forcing an Experiment Center variant

> [!NOTE]
> [Experiment Center](https://auth0.com/docs/customize/experiment-center/overview) support via SDKs is currently in Early Access. To request access to this feature, contact your Auth0 representative.

Experiment Center lets you A/B test your login flow. To force a specific variant - for testing or to apply a decision from a feature-flag service - pass `experiment_id` and `variation_id` via `authorizationParams`. Auth0 will use them instead of its server-side deterministic assignment. Both IDs are obtained from your Auth0 Dashboard or the Management API. You can also pass the optional `segment_id` when the experiment uses segment targeting.

```jsx
import { useAuth0 } from '@auth0/auth0-react';

function LoginButton() {
const { loginWithRedirect } = useAuth0();

return (
<button
onClick={() =>
loginWithRedirect({
authorizationParams: {
experiment_id: '<EXPERIMENT_ID>',
variation_id: '<VARIATION_ID>',
// segment_id is optional
segment_id: '<SEGMENT_ID>',
},
})
}
>
Log in
</button>
);
}
```

The same params work with `loginWithPopup`:

```jsx
import { useAuth0 } from '@auth0/auth0-react';

function LoginButton() {
const { loginWithPopup } = useAuth0();

return (
<button
onClick={() =>
loginWithPopup({
authorizationParams: {
experiment_id: '<EXPERIMENT_ID>',
variation_id: '<VARIATION_ID>',
// segment_id is optional
segment_id: '<SEGMENT_ID>',
},
})
}
>
Log in
</button>
);
}
```

> [!IMPORTANT]
> Pass these parameters per call on `loginWithRedirect` (or `loginWithPopup`), not on `Auth0Provider`'s `authorizationParams`. Setting them on the provider pins every login including silent `prompt=none` token-renewal calls - to the same variation, which cancels the A/B test. Experiment Center does not run on silent checks.

- **Testing:** drive the IDs from test automation (e.g. Cypress/Playwright) using values from a CI environment variable against a staging tenant. Do not hard-code them in shipped app code.
- **Production:** pass the variant decision from a feature-flag tool (e.g. LaunchDarkly) that has already decided which variant the user should see for this request.

The override applies only to this request; the next login without these params reverts to normal server-side assignment.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ function App() {
export default App;
```

### Experiment Center

> [!NOTE]
> [Experiment Center](https://auth0.com/docs/customize/experiment-center) support via SDKs is currently in Early Access. To request access to this feature, contact your Auth0 representative.

You can also force a specific Experiment Center variant per login by passing `experiment_id`, `variation_id`, and the optional `segment_id` in `authorizationParams` - see the [Experiment Center example](https://github.com/auth0/auth0-react/blob/main/EXAMPLES.md#forcing-an-experiment-center-variant).

For more code samples on how to integrate **auth0-react** SDK in your **React** application, have a look at our [examples](https://github.com/auth0/auth0-react/blob/main/EXAMPLES.md).

## API reference
Expand Down
104 changes: 104 additions & 0 deletions __tests__/auth-provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,110 @@ describe('Auth0Provider', () => {
});
});

it('should forward Experiment Center params through loginWithRedirect', async () => {
Comment thread
siddhikotak marked this conversation as resolved.
const wrapper = createWrapper();
const { result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitFor(() => {
expect(result.current.loginWithRedirect).toBeInstanceOf(Function);
});
await result.current.loginWithRedirect({
authorizationParams: {
redirect_uri: '__redirect_uri__',
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
segment_id: '__segment_id__',
},
});
expect(clientMock.loginWithRedirect).toHaveBeenCalledWith({
authorizationParams: {
redirect_uri: '__redirect_uri__',
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
segment_id: '__segment_id__',
},
});
});

it('should forward Experiment Center params through loginWithRedirect when segment_id is omitted', async () => {
const wrapper = createWrapper();
const { result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitFor(() => {
expect(result.current.loginWithRedirect).toBeInstanceOf(Function);
});
await result.current.loginWithRedirect({
authorizationParams: {
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
},
});
expect(clientMock.loginWithRedirect).toHaveBeenCalledWith({
authorizationParams: {
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
},
});
});

it('should forward Experiment Center params through loginWithPopup', async () => {
const wrapper = createWrapper();
const { result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitFor(() => {
expect(result.current.loginWithPopup).toBeInstanceOf(Function);
});
await result.current.loginWithPopup({
authorizationParams: {
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
segment_id: '__segment_id__',
},
});
expect(clientMock.loginWithPopup).toHaveBeenCalledWith(
{
authorizationParams: {
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
segment_id: '__segment_id__',
},
},
undefined
);
});

it('should forward Experiment Center params through loginWithPopup when segment_id is omitted', async () => {
const wrapper = createWrapper();
const { result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitFor(() => {
expect(result.current.loginWithPopup).toBeInstanceOf(Function);
});
await result.current.loginWithPopup({
authorizationParams: {
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
},
});
expect(clientMock.loginWithPopup).toHaveBeenCalledWith(
{
authorizationParams: {
experiment_id: '__experiment_id__',
variation_id: '__variation_id__',
},
},
undefined
);
});

it('should provide a login method supporting redirectUri', async () => {
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
const wrapper = createWrapper();
Expand Down
Loading