Frameworks
What each framework integration does for you, and how the typed client, React, Vue, Next.js, Nuxt, SvelteKit, React Router, NestJS, and Node ones 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 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 , , , , and
, and write the same audit events.
Why use an integration
You can always call the core directly: mount iam.handler, pass a request's headers as the 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-Cookieheaders 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
POSTmust be checked for itsOrigin(a CSRF check). Most server integrations run the same rule the IAM handler uses; 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).
Pick an integration
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, Hono, Fastify | @better-iam/middleware (better-iam/express, /hono, /fastify) | req.iam.getSession() | requireSession(), authorize() | req.iam.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 for the full import map.
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.
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 , , and protocol mounts. Next.js exports it from a catch-all route handler, Nuxt mounts it as a Nitro route, SvelteKit serves it fromhandle, 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.sessionon HTTPS,better-iam.sessionon 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 withiam.authorize). Batched advisory checks, such as Next.jsallowed()andreq.iam.can(), go throughauthorizeManywith at most 50 checks per call and one call per . The 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.handleras its transport. It forwards the caller's cookies and forwarding headers, setsOriginto the IAM origin, and writes theSet-Cookieheaders 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 hasIamServiceinstead. - CSRF checks. Cookie-authenticated
POST,PUT,PATCH, andDELETErequests must come from the application's own origin (orSec-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 for where each integration applies it. - Step-up.
stepUp: { mfa?: true | 'fresh', maxAgeMs?: number }asks for the same way in the Next.js, SvelteKit, React Router, and Node guards, through the sharedcheckStepUprules. Failures carryMFA_REQUIRED,RECENT_AUTH_REQUIRED, orIMPERSONATION_RESTRICTEDand areasonofmfa,recent, orimpersonation. 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 : 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
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) |
| 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
Each card shows the package's version, its description, its subpath entries, and the framework versions it expects (peer dependencies).
@better-iam/client0.1.0Typed browser client with session store and passkey helpers.
/passkeys/session@better-iam/middleware0.1.0Framework-neutral middleware core with Express, Hono, and Fastify adapters.
/express/hono/fastify@better-iam/nestjs0.1.0NestJS module, guard, decorators, and testing utilities.
/testingPeers: @nestjs/common, @nestjs/core, reflect-metadata, rxjs
@better-iam/next0.1.0Next.js App Router helpers: guarded pages, routes, actions, and edge checks.
/edge/clientPeers: next, react
@better-iam/nuxt0.1.0Nuxt module and h3 helpers.
/h3Peers: nuxt, vue
@better-iam/react0.1.0React provider, hooks, and permission-gated components.
Peers: react
@better-iam/react-router0.1.0React Router (framework mode) middleware, guarded loaders, and actions.
Peers: react-router
@better-iam/svelte0.1.0Svelte stores and SvelteKit hooks, guards, and actions.
/kitPeers: @sveltejs/kit, svelte
@better-iam/vue0.1.0Vue plugin, composables, and the `IamCan` component.
Peers: vue
Choose your stack
Typed client
createIamClient for any browser or Node code, the session store, and passkey helpers.
React
IamProvider, useSession, useAuthorize, Can, and self-service hooks.
Vue
The Vue plugin, composables, IamCan, and server-rendering hydration.
Next.js
App Router guards, server actions, auth forms, middleware, and the Pages Router.
Nuxt
A Nuxt module with page meta guards, server utilities, and hydrated sessions.
SvelteKit
A handle hook, locals.iam, guarded loads and form actions, and Svelte stores.
React Router
Framework-mode middleware, guarded loaders and actions, and the API resource route.
NestJS
A module, a guard, @Authorize decorators, audit event handlers, and assertions.
Express, Hono, Fastify
Middleware that serves the API, adds req.iam, and guards routes.
Was this page helpful?
Last updated on