# Use authentication in React
Use `@udibo/oauth2/react` to share session state and sign-in actions with your
components. For a browser app with a backend, connect it to `BffClient` so
tokens stay on the server. First mount your BFF using
[the integration guide](https://www.udibo.com/docs/oauth2/guides/use-udibo) or the
[app-owned authorization example](https://www.udibo.com/docs/oauth2/examples/hono/app-with-own-auth).
## Provide the client once
```tsx
import { BffClient } from "@udibo/oauth2/client";
import { OAuth2Provider, RequireAuth, useOAuth2 } from "@udibo/oauth2/react";
const client = new BffClient();
function Account() {
const { user, logout } = useOAuth2();
return (
Signed in as {String(user?.name ?? user?.sub ?? "user")}
);
}
export function App() {
return (
Checking your session…
}>
);
}
```
`RequireAuth` starts sign-in when the session is unauthenticated. It starts none
while a `logout()` from `useOAuth2()` is signing out, so a sign-out button on a
protected page ends at the logout destination rather than the login page. The
guard covers only that hook: calling `client.logout()` directly bypasses it. If
the logout navigation never completes, for example a `beforeunload` handler
cancels it or the logout URL answers `204` or a download, the page stays on
`fallback` until it is reloaded. Use
`useOAuth2()` directly when a page should stay public and offer an explicit
sign-in button. Construct the client once per browser app, not on every render.
For server rendering, derive any initial auth state from the current request; do
not share a token-owning `DirectClient` across users.
With a `HonoBff`, `bff.readSession(c)` gives the server the answer
`GET /auth/session` gives the browser, without tokens. Pass it to the provider
as `initialState`, and a signed-in page renders signed in on the first paint
instead of waiting for the session probe:
```tsx
import type { SessionState } from "@udibo/oauth2/client";
import { BffClient } from "@udibo/oauth2/client";
import { OAuth2Provider } from "@udibo/oauth2/react";
import type { ReactNode } from "react";
const client = new BffClient();
export function Root(
props: { session: SessionState; children: ReactNode },
) {
return (
{props.children}
);
}
```
`readSession` applies the same session lifetime as the probe. In
`sessionMode:
"own"`, it destroys a session past `sessionMaxAgeMs` and clears
its cookie. Shared mode leaves lifetime enforcement to the application and its
session store. It sets no caching policy, so send a page rendered from its
answer with a private or `no-store` `Cache-Control`.
While signed in, the provider re-checks the session shortly before
`sessionExpiresIn` elapses. A BFF session whose access token has expired reports
`sessionExpiresIn: 0` until a proxied call refreshes the token, so the provider
backs off rather than polling: it checks after 5 seconds, then doubles the wait
after each check that fails or reports no later expiry, up to one check every 5
minutes per tab. A check that reports a later expiry returns it to the
pre-expiry schedule, never more often than every 5 seconds. A `BffClient.fetch` that gets a `401` still signs the UI
out immediately.
A rendering guard does not protect data. Keep bearer/session validation,
required scopes, and record-level authorization on the server. Use the same
`BffClient.fetch` for credentialed API calls so the CSRF header is included.
## Forms for an app that hosts its own login
Applications using Udibo redirect to hosted sign-in. Use the optional components
below only on login pages your own application hosts. They provide fields and
submission state; they do not create routes, sessions, or an identity service.
| Component | Use |
| ----------------------------------------------- | ----------------------------------------- |
| `SignInForm`, `SignUpForm` | App-owned password login and registration |
| `RequestPasswordResetForm`, `ResetPasswordForm` | Request and complete a password reset |
| `MfaEnrollmentForm`, `MfaChallengeForm` | Display enrollment and challenge controls |
| `UserMenu` | User display and sign-out action |
```tsx
import { SignInForm } from "@udibo/oauth2/react/components";
export function SignInPage() {
return (
{
const response = await fetch("/auth/signin", {
method: "POST",
headers: { "content-type": "application/json", "x-csrf": "1" },
body: JSON.stringify(values),
});
if (!response.ok) {
return {
error: "Sign-in failed. Check your credentials.",
};
}
}}
onSuccess={() => location.assign("/")}
/>
);
}
```
This assumes your application has mounted that sign-in endpoint. Validate the
request and create a session in the backend's authentication hook. If MFA is
enabled, route first-factor success to the challenge instead of immediately
navigating to an authenticated page; see [MFA](https://www.udibo.com/docs/oauth2/guides/add-mfa).
`onSubmit` returns nothing for success or `{ error, fieldErrors }` for a form
failure. `onSuccess` is a UI side effect, not an authorization gate; errors in
that callback are isolated. For a no-JavaScript form post, `action`/`method`
identify the backend route and that route must return the appropriate redirect.
## Styling and custom markup
Components are unstyled. Use `className`, the per-slot `classNames` map, or
stable `data-oauth2-*` attributes. `AuthFormClassNamesProvider` supplies shared
form classes; an individual form can override a slot. `UserMenu` has its own
styling props.
While a submit is in flight, the submit button shows its pending label and
carries `aria-disabled="true"` instead of `disabled`, so it keeps keyboard
focus; repeat clicks, Enter, and Space are ignored until the submit settles.
Style the pending state with `[data-oauth2-submit][aria-disabled="true"]`, not
`:disabled`. Custom markup built on `useAuthForm` gets the same guard from
`handleSubmit`; mark its button with
`aria-disabled={form.isSubmitting || undefined}`.
For full markup control, use `useAuthForm` or a component's render-prop
children. Keep labels, error associations, focus behavior, and autocomplete
attributes when replacing markup. The
[component API](https://jsr.io/@udibo/oauth2/doc/react/components) lists the
slots and props.
See the [React Router template](https://www.udibo.com/docs/oauth2/templates/react-router) for a
complete app, and [testing](https://www.udibo.com/docs/oauth2/guides/testing) for mock providers and BFF session
fixtures.