--- name: frontmcp-auth-ui description: 'Use when customizing, branding, or replacing the built-in FrontMCP OAuth pages (the login, consent, federated-select, incremental-authorization, and error pages) with your own React components. Covers the auth.ui slot-to-file map and auth.extras name-to-handler map on the auth config (no decorator, no class); the @frontmcp/ui/auth React hooks, the AuthPageWrapper component, and mountAuthPage (client-rendered via an esm.sh import-map plus a per-file server-side transform, with no bundling and no SSR); and the framework-owned CSRF and CSP. Triggers: custom login page, brand the consent screen, replace the OAuth UI, custom authorization UI, style the auth pages, multi-step login. The skill for CUSTOM AUTHORIZATION UI (distinct from auth config in frontmcp-config and permissions in frontmcp-authorities).' tags: [auth, auth-ui, login, consent, custom-ui, react, oauth, guide] category: config targets: [all] bundle: [full] priority: 5 visibility: both license: Apache-2.0 metadata: docs: https://docs.agentfront.dev/frontmcp/authentication/custom-ui --- # FrontMCP Custom Authorization UI (`auth.ui`) Entry point for replacing FrontMCP's built-in OAuth pages (login, consent, federated, incremental, error) with your own React components. Custom UI is a simple **slot→file map** (`auth.ui`) plus an extras **name→handler map** (`auth.extras`) on the auth config — there is no decorator and no class. The `references/custom-auth-ui.md` reference has the full API; the `examples/` show a single login slot and a multi-step extras form. ## When to Use This Skill ### Must Use - Branding or fully replacing the `local`/`remote` mode **login** page with a custom React component - Building a custom **consent** screen, **federated** provider picker, **incremental** authorization, or **error** page - Adding a server-validated multi-step field to an authorization page (e.g. "add another item") via `auth.extras` ### Recommended - Understanding which half is the server (`auth.ui` / `auth.extras` in `@frontmcp/sdk`) and which is the client (`@frontmcp/ui/auth`) - Looking up the `AuthFlowState` fields a slot component receives, or the `/oauth/ui/extra` route - Confirming that the framework (not your component) owns CSRF + CSP ### Skip When - You only need to add/rename **fields** on the built-in login page or run a custom verifier — use the declarative `login` / `authenticate` config in `frontmcp-config` → `configure-auth` instead (no React, no build step) - You are customizing a **tool widget** (not an auth page) — use `create-tool` → `ui-widgets` - You don't need a custom page at all — configuring no `auth.ui` keeps the built-in pages > **Decision:** Use this skill when you want to render your OWN component for an auth slot. Use `configure-auth`'s declarative `login` config when tweaking the built-in page's fields is enough. ## Prerequisites - A FrontMCP server in `local` or `remote` auth mode (see `frontmcp-config` → `configure-auth`) - `@frontmcp/ui`, `react`, and `react-dom` installed (`react`/`react-dom` are peer deps of `@frontmcp/ui`) ## Steps 1. Write a React component (default export) for the slot, reading the injected state via `@frontmcp/ui/auth` hooks (see `references/custom-auth-ui.md`) 2. Map the slot to its `.tsx` path under `auth.ui` — a RELATIVE path auto-anchored to the config file's directory (no `fileURLToPath`) 3. (Optional) Add an `auth.extras[name]` handler function for any mid-flow validated field 4. Declare both **under `auth`**: `@FrontMcp({ auth: { mode: 'local', ui: { login: './…' }, extras: { … } } })` (per-app under `splitByApp` — put them on the `@App({ auth })` that owns the pages) 5. Verify using the Verification Checklist below ## Scenario Routing Table | Scenario | Reference | Description | | -------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------ | | Replace any built-in auth page with a React component | `custom-auth-ui` | `auth.ui: { slot: './file.tsx' }`, the hooks, ``, `mountAuthPage` | | Add a server-validated mid-flow field (multi-step form) | `custom-auth-ui` | `auth.extras: { name: handler }`, the accumulator, `useExtraField` / `useAddedItems` | | Look up the flow-state fields a component receives | `custom-auth-ui` | `AuthFlowState` field table (no PII) | | Understand the served route and the framework-owned CSRF + CSP | `custom-auth-ui` | `/oauth/ui/extra`, the esm.sh import-map + inline module, security ownership | ## The map form Custom UI is a **slot→file map** on the auth config. Point each slot at a sibling `.tsx`/`.jsx` source (default export); the SDK transpiles it once server-side and inlines it as an ES module, with deps loaded from esm.sh via an import-map and `mountAuthPage` appended for you — exactly as `@Tool({ ui })` leads with the `FileSource` `{ file }` form: ```ts // src/server.ts import { App } from '@frontmcp/sdk'; // or @FrontMcp @App({ auth: { mode: 'local', // slot → RELATIVE .tsx, auto-anchored to THIS config file's directory. ui: { login: './auth/login.tsx' }, // extra name → handler fn (no class). extras: { 'envs:add': async (input, ctx) => ({ ok: true, addedItems: [{ key: String(input.key) }] }) }, }, }) export default class Server {} ``` A relative `auth.ui` path resolves against the directory of the file that declares the `@App`/`@FrontMcp` config — captured automatically at decoration time. **No `fileURLToPath` needed.** Absolute paths pass through; on capture failure the framework falls back to `process.cwd()` with a warning. ## Common Patterns | Pattern | Correct | Incorrect | Why | | ------------------ | ---------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | Slot registration | `auth: { ui: { login: './login.tsx' } }` | `auth: { ui: [LoginAuthUi] }` (array of classes) | Custom UI is a slot→file MAP now — no decorator, no class | | Path anchoring | Relative `'./login.tsx'` (auto-anchored to the config file) | `fileURLToPath(new URL('./login.tsx', import.meta.url))` | The framework captures the config file's dir for you — manual anchoring is no longer needed | | Extra registration | `auth: { extras: { 'envs:add': handlerFn } }` | `auth: { extras: [AddEnvExtra] }` (annotated class) | Extras are an extra-name → handler-function MAP now | | CSRF | Let the hooks round-trip `csrfToken` | Generating / checking a token in your component | The server mints + verifies CSRF; your component must not | | User identity | User-typed fields live in your own `
` inputs | Putting email/name into `AuthFlowState` | `AuthFlowState` is PII-free by contract — it carries OAuth client ids + control fields only | | Wrapper form | Wrap UI in `` (renders the finish `` + hidden fields) | Hand-rolling `pending_auth_id` / `csrf` hidden inputs | The wrapper injects the control fields so a no-JS submit still works | ## Verification Checklist - [ ] `GET /oauth/authorize` returns a page with an EMPTY `#frontmcp-auth-root` (not the built-in page) — the component's rendered markup is NOT in the HTTP response - [ ] The page has a `