Middleware
Redirect signed-out visitors at the edge with createIamMiddleware, keep public paths open, and forward the requested path so ?next= fills itself.
createIamMiddleware from @better-iam/next/edge sends visitors without a cookie to the login page
before a protected page starts rendering, and forwards the requested path to your server components. It is a
routing convenience, not authorization.
Why use it: a signed-out visitor is redirected at the edge, before any server component runs or queries anything. Also, App Router server components are not given the full path of the request they render. Without the forwarded path, a guard deep in a layout would not know where to send the person back after sign-in.
Middleware checks presence, not validity
Middleware runs on the edge and cannot open the database. It only checks that the session cookie is present:
__Host-better-iam.session on HTTPS and better-iam.session on loopback HTTP. Pages must still authenticate
with requireSession, page, or requireTenantSession. A stale cookie gets through the middleware, and the
page's guard then redirects.
Setup
import { NextResponse } from 'next/server';
import { createIamMiddleware } from '@better-iam/next/edge';
export const middleware = createIamMiddleware({
loginPath: '/login',
publicPaths: ['/', '/pricing', '/docs/**', '/invite/*'],
signedInRedirect: '/dashboard',
next: (init) => NextResponse.next(init),
});
export const config = { matcher: ['/((?!_next|favicon.ico).*)'] };Import middleware helpers from @better-iam/next/edge. That entry has no Node, React, or database imports, so the
edge bundle stays small. The main entry re-exports everything in it.
Prop
Type
What it does, per request
- Public paths. The default rule protects everything except the login path (and paths below it), anything
under
/api/iam, and thepublicPathsglobs.publicPathsadds to those defaults;protectreplaces the whole rule. - Redirects. A protected request without the cookie gets a 307 to
loginPathwith?next=set to the path and query it asked for. - Path forwarding. With
next, every request that continues carriesx-better-iam-pathname.requireSession,page, andrequireTenantSessionuse it as the default?next=, so a deep link survives sign-in without passingreturnToby hand.iamNext.currentPath()reads it (when present and safe).
Signed-in redirects and stale cookies
signedInRedirect sends visitors who carry a cookie away from a bare login page, for example to the dashboard. A
login URL with ?next= always renders.
That rule is what keeps a stale cookie from looping. When a server guard rejects a request that still carries a
session cookie (revoked, expired, or from a reset database), it always attaches ?next= (at least /). The
middleware sees ?next=, lets the login page render, and the visitor signs in again instead of bouncing between
the guard and the middleware.
Safe redirects
safeRedirectPath(value, fallback = '/') returns value only when it is a same-origin path. It rejects absolute
URLs, protocol-relative //host, backslashes, control characters, values over 2048 characters, and dot segments
that normalize into //host. Run every next parameter through it before redirecting:
import { safeRedirectPath } from '@better-iam/next/edge';
redirect(safeRedirectPath(searchParams.get('next'), '/dashboard'));The auth forms and guards already do this. The edge entry also exports matchPath(pattern, pathname), the glob
matcher behind publicPaths, and sessionCookieName(secure), which returns the cookie name the middleware looks
for.
Next steps
Better IAM is created by Sean Filimon
Last updated
Organizations in the URL
Serve each organization under its own /[org] path with requireTenantSession, and send visitors from other organizations to the right sign-in.
Advanced
Step-up, API keys in route handlers, assertions and webhooks for other services, client refresh, the Pages Router, and background delivery in Next.js.