# Integrations (/docs/integrations)

> Every Better IAM framework integration (full-stack, backend, and client) with what each one provides and how they compare.



Every framework integration wraps the same `betterIam()` instance. None of them has its own session format,
policy engine, or audit trail. They read the session the server issued, ask the server for decisions, and call
the server's HTTP handler in process when they need to sign someone in. A page guarded in Next.js, a NestJS
controller, and an Express route therefore enforce the same roles, policies, conditions, boundaries, and
relationships, and write the same audit events.

## Choose your stack [#choose-your-stack]

### Full stack [#full-stack]

Frameworks that render pages on the server and run in the browser. These integrations mount the IAM API, read the
session in server code, guard pages and mutations, and hand the session to the browser for hydration.

  - [Next.js](/docs/integrations/nextjs): App Router guards, server actions, auth forms, middleware, and the Pages Router.

  - [Nuxt](/docs/integrations/nuxt): A Nuxt module with page meta guards, server utilities, and hydrated sessions.

  - [SvelteKit](/docs/integrations/sveltekit): A `handle` hook, `locals.iam`, guarded loads and form actions, and Svelte stores.

  - [React Router](/docs/integrations/react-router): Framework-mode middleware, guarded loaders and actions, and the API resource route.

### Backend [#backend]

Server frameworks with no browser side of their own. These integrations mount the IAM API, add per-request
helpers, and guard routes.

  - [Express](/docs/integrations/express): Middleware that serves the API, adds `req.iam`, and guards routes.

  - [Hono](/docs/integrations/hono): Middleware on the fetch handler, `c.get('iam')`, and guards, for any runtime Hono runs on.

  - [Fastify](/docs/integrations/fastify): A plugin that answers the API before body parsing, `request.iam`, and `preHandler` guards.

  - [NestJS](/docs/integrations/nestjs): A module, a guard, `@Authorize` decorators, audit event handlers, and assertions.

  - [Node.js and custom servers](/docs/integrations/node): `iam.nodeHandler`, `iam.handler`, and the framework-neutral core for any other server.

### Client [#client]

Browser packages that decide what to render. They pair with any of the servers above.

  - [React](/docs/integrations/react): `IamProvider`, `useSession`, `useAuthorize`, `Can`, and self-service hooks.

  - [Vue](/docs/integrations/vue): The Vue plugin, composables, `IamCan`, and server-rendering hydration.

  - [Typed client](/docs/concepts/client): `createIamClient` for any browser or Node code, the session store, and passkey helpers.

## Why use an integration [#why-use-an-integration]

You can always call the core directly: mount `iam.handler`, pass a request's headers as the credential to
`iam.api.*`, and call `iam.require` before a mutation. The integrations exist because every web framework needs
the same glue around those calls, and the glue is where mistakes creep in:

* **Getting the credential.** Each request carries a session cookie or a bearer token. The integration reads it
  the way your framework exposes requests, and looks the session up once per request instead of once per call.
* **Answering refusals the framework's way.** A signed-out visitor on a page should be redirected to the login
  page with a safe `?next=`. The same refusal on an API route should be a 401 JSON body, and in a form action it
  should come back to the form. The integrations map every IAM refusal to the right shape.
* **Setting cookies from the server.** When a server-side form signs someone in, the new session cookie has to
  land on the response with the attributes the server chose. The integrations run sign-in through the IAM HTTP
  handler in process and copy its `Set-Cookie` headers through your framework's cookie API.
* **Cross-site protection.** Browsers attach cookies automatically, even to a form another site's page submits,
  so a cookie-authenticated `POST` must be checked for its `Origin` (a CSRF check). Most server integrations run
  the same rule the IAM handler uses; [Cross-site checks](#cross-site-checks) lists where each one does it.
* **Fast, consistent UI.** Permission checks for a page full of buttons are batched into one call. Sessions and
  decisions loaded on the server are handed to the browser, so the first render needs no extra requests. It also
  matches the server's HTML, so there is no hydration mismatch (a browser render that differs from the HTML).

## Compare integrations [#compare-integrations]

Choose the package for the framework your application runs on. The browser packages (the typed client, React,
and Vue) decide what to render and pair with any server. The full-stack and server packages mount the API, read
sessions on the server, and enforce access.

How to read the columns:

* **Session on the server**: how server code reads the signed-in person.
* **Guards**: what enforces access, or in the browser, decides what to show.
* **Mutations**: where state changes are checked.
* **Server rendering**: how a session loaded on the server reaches the browser for hydration.

| Stack                                              | Package (umbrella subpath)                                 | Session on the server                    | Guards                                            | Mutations                           | Browser                               | Server rendering            |
| -------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------- | ----------------------------------- | ------------------------------------- | --------------------------- |
| Any browser app                                    | `@better-iam/client` (`better-iam/client`)                 | not applicable                           | not applicable                                    | every API method                    | typed client, session store, passkeys | not applicable              |
| React                                              | `@better-iam/react` (`better-iam/react`)                   | not applicable                           | `Can`, `useAuthorize` (advisory)                  | through the client                  | `IamProvider`, hooks                  | `initialSession`            |
| Vue                                                | `@better-iam/vue` (`better-iam/vue`)                       | not applicable                           | `IamCan`, `useCan` (advisory)                     | through the client                  | plugin, composables                   | `createHydration`           |
| Next.js                                            | `@better-iam/next` (`better-iam/next`, `/edge`, `/client`) | `getSession`, `requireSession`           | `page`, `route`, `apiRoute`, middleware           | `action`, `authActions`, `client()` | `IamNextProvider`                     | `sessionForClient`          |
| Nuxt                                               | `@better-iam/nuxt` (not in the umbrella), `/h3`            | `getIamSession(event)`                   | `definePageMeta({ iam })`, `requireIamAccess`     | server routes                       | auto-imported composables, `IamCan`   | automatic payload hydration |
| SvelteKit                                          | `@better-iam/svelte` (`better-iam/svelte`, `/kit`)         | `locals.iam.getSession()`                | `protect` rules, `guard`                          | `action` returns `fail()`           | Svelte stores                         | `sessionData`, `initial`    |
| React Router                                       | `@better-iam/react-router` (`better-iam/react-router`)     | `helpers(args).getSession()`             | `guard`                                           | `action` returns `data()`           | `@better-iam/react`                   | `sessionData`               |
| NestJS                                             | `@better-iam/nestjs` (`better-iam/nestjs`)                 | `@CurrentPrincipal()`                    | `IamGuard`, `@Authorize`                          | `IamService.require`                | not applicable                        | not applicable              |
| [Express](/docs/integrations/express)              | `@better-iam/middleware` (`better-iam/express`)            | `req.iam.getSession()`                   | `requireSession()`, `authorize()`                 | `req.iam.require()`                 | not applicable                        | not applicable              |
| [Hono](/docs/integrations/hono)                    | `@better-iam/middleware` (`better-iam/hono`)               | `c.get('iam').getSession()`              | `requireSession()`, `authorize()`                 | `c.get('iam').require()`            | not applicable                        | not applicable              |
| [Fastify](/docs/integrations/fastify)              | `@better-iam/middleware` (`better-iam/fastify`)            | `request.iam.getSession()`               | `requireSession()`, `authorize()` as `preHandler` | `request.iam.require()`             | not applicable                        | not applicable              |
| [Node.js, custom servers](/docs/integrations/node) | `@better-iam/middleware` (`better-iam/middleware`)         | `createRequestHelpers(...).getSession()` | `enforceGuard()`                                  | `helpers.require()`                 | not applicable                        | not applicable              |

The umbrella package `better-iam` installs everything and exposes each integration as a subpath. The scoped
packages (`@better-iam/next`, ...) are the same code for applications that install only what they use.
`@better-iam/nuxt` is the exception: install it directly, because it is a Nuxt module and is not part of the
umbrella. See [Installation](/docs/installation) for the full import map.

## How they share one core [#how-they-share-one-core]

Browser code reaches the instance over HTTP, through the API your server mounts. Server code (guards, loaders,
actions) calls the same instance directly in the same process, with no network hop.

<Mermaid
  chart="flowchart LR
  subgraph Browser
    C[Typed client] -->|&#x22;POST /api/iam/{group}/{method}&#x22;| M
    H[&#x22;Hooks, stores, IamCan / Can&#x22;] --> C
  end
  subgraph Server[Your server]
    M[Mounted IAM handler]
    G[&#x22;Guards: page, route, guard, @Authorize&#x22;] -->|&#x22;iam.require / authorizeMany&#x22;| Core
    I[&#x22;In-process client: sign-in, sign-out&#x22;] -->|&#x22;iam.handler, no network hop&#x22;| Core
    M --> Core[betterIam instance]
  end
  Core --> DB[(Your database)]
  Core --> A[Audit chain and events]"
/>

The server-side integrations are built from the same pieces. Not every integration has every piece; the notes
in each item say which ones do.

* **A mounted HTTP API.** The browser client needs somewhere to send its calls. The IAM handler serves
  `POST {basePath}/{group}/{method}` (default `/api/iam`), plus `/health`, `/metrics`, and the OAuth and
  OpenID Connect, SAML, and SCIM protocol mounts. Next.js exports it from a catch-all route handler, Nuxt
  mounts it as a Nitro route, SvelteKit serves it from `handle`, React Router from a resource route, NestJS
  mounts it as middleware, and the Node adapters answer it before your routes run.
* **Sessions from the request.** Server helpers read the session cookie (`__Host-better-iam.session` on HTTPS,
  `better-iam.session` on loopback HTTP) or a bearer token, and memoize the lookup for the request. Missing,
  expired, revoked, and step-up-required credentials read as signed out instead of throwing.
* **Server-side decisions.** Guards enforce with `iam.require` (NestJS records each rule with `iam.authorize`).
  Batched advisory checks, such as Next.js `allowed()` and `req.iam.can()`, go through `authorizeMany` with at
  most 50 checks per call and one call per tenant. The resource defaults to the tenant itself (`iam/{tenantId}`) and the
  tenant to the session's.
* **An in-process client.** Next.js, SvelteKit, React Router, and the Node adapters give server code the full
  typed client with `iam.handler` as its transport. It forwards the caller's cookies and forwarding headers, sets
  `Origin` to the IAM origin, and writes the `Set-Cookie` headers the server returns through the framework's own
  cookie API. That is what lets a plain `<form>` sign someone in without client JavaScript. Nuxt's server
  rendering uses a smaller bound client (session and decisions only), and NestJS has `IamService` instead.
* **CSRF checks.** Cookie-authenticated `POST`, `PUT`, `PATCH`, and `DELETE` requests must come from the
  application's own origin (or `Sec-Fetch-Site: same-origin`), the IAM origin, or a configured trusted origin.
  Bearer credentials skip the check because browsers never attach them on their own. See
  [Cross-site checks](#cross-site-checks) for where each integration applies it.
* **Step-up.** `stepUp: { mfa?: true | 'fresh', maxAgeMs?: number }` asks for step-up authentication the same way in the
  Next.js, SvelteKit, React Router, and Node guards, through the same `checkStepUp` rules (Next.js ships its own copy). Failures carry
  `MFA_REQUIRED`, `RECENT_AUTH_REQUIRED`, or `IMPERSONATION_RESTRICTED` and a `reason` of `mfa`, `recent`, or
  `impersonation`. NestJS offers `@RequireMfa()`.
* **Safe redirects.** `safeRedirectPath(value, fallback)` (Next.js, SvelteKit, React Router, and the Node
  adapters) accepts only same-origin paths, so a `?next=` parameter cannot become an open redirect.

> **Rendering decisions are advisory.** 
  `Can`, `IamCan`, `useAuthorize`, the Svelte stores, and `iamNext.allowed()` make advisory decisions: they decide what to render. Enforce the
  operation itself on the server, immediately before performing it, with a guard or `iam.require`, and load
  resource ownership from trusted storage.

## Cross-site checks [#cross-site-checks]

A CSRF (cross-site request forgery) attack makes a signed-in visitor's browser send a request to your
application from another site's page, and the browser attaches the session cookie on its own. Session cookies
are `SameSite=Lax` by default, which blocks most of these. Browsers still send them with form posts from sibling
subdomains (`evil.example.com` posting to `app.example.com`), though. The IAM HTTP API refuses such requests
itself. Your own routes need the same check, and this is where each integration provides it:

| Integration              | Where the `Origin` of cookie-authenticated mutations is checked                                                                     |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| Typed client, React, Vue | Not applicable: they run in the browser. The client sends the `X-Better-IAM: 1` header the IAM API requires                         |
| Next.js                  | `route()`, `apiRoute()`, and `pages.api()`. Server actions are checked by Next itself                                               |
| Nuxt                     | The mounted IAM API only. Check your own state-changing server routes yourself ([how](/docs/integrations/nuxt#cross-site-requests)) |
| SvelteKit                | SvelteKit's own origin check for form posts (`csrf.checkOrigin`, on by default)                                                     |
| React Router             | `guard()` and `action()` (the `csrf` option)                                                                                        |
| NestJS                   | `IamGuard` (the `csrf` module option)                                                                                               |
| Express, Hono, Fastify   | `requireSession()` and `authorize()` guards (the `csrf` option); `checkRequestOrigin` for unguarded routes                          |

## Packages [#packages]

Each card shows the package's version, its description, its subpath entries, and the framework versions it
expects (peer dependencies).

