# 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.