Next.js
masksNext from @masks/server/next covers the App Router. It reads the
settings every integration shares.
Setting up
Section titled “Setting up”-
Create one
Masksfor the app:lib/masks.ts import { masksNext } from "@masks/server/next";export const { handlers, auth, requireAuth, withMasks, proxy, masks } =masksNext({name: "Catalog",scope: ["openid", "profile", "email", "offline_access", "organization"],privileges: { admin: ["catalog:admin"] },protect: ["/dashboard"],}); -
Answer every route under
/authwith an optional catch-all route:app/auth/[[...masks]]/route.ts import { handlers } from "@/lib/masks";export const { GET, POST, DELETE } = handlers; -
Add the proxy, which refreshes a session before a page renders:
proxy.ts export { proxy } from "@/lib/masks";export const config = {matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],}; -
Set
MASKS_ISSUER,MASKS_RESOURCE, andMASKS_SECRET, start the app, and open/auth. The first visit starts the handshake.
Guarding pages and routes
Section titled “Guarding pages and routes”requireAuth guards a page:
import { requireAuth } from "@/lib/masks";
export default async function Dashboard() { const masked = await requireAuth();
return <h1>Hello, {masked.name}</h1>;}withMasks guards a route handler, by session or by access token:
import { withMasks } from "@/lib/masks";
export const GET = withMasks(async (request, { masked }) => Response.json({ subject: masked.subject }),);A server component cannot set cookies, and masks revokes a refresh token that is used twice, so
auth and requireAuth never refresh. The proxy refreshes the session first and hands the fresh
cookie to the page. Without the proxy, requireAuth sends a person whose token expired through
/auth/refresh.
requireAuth sends a signed-out person to sign in, and asks masks once for a privilege’s missing
scopes. It throws a ForbiddenError for a privilege the person does not hold, which the nearest
error.tsx shows.
After a sign-in, requireAuth sends the person back to the path the proxy recorded. Without the
proxy, name the page first, as in requireAuth({ returnTo: "/billing" }, "admin"). Otherwise the
sign-in comes back to /.
Reference
Section titled “Reference”masksNext(options) takes the options every integration
shares, and two more:
| Option | |
|---|---|
protect |
Paths, or regular expressions, whose signed-out visitors the proxy sends to sign in. A path also covers every path under it. |
masks |
A Masks the app already built, used in place of a new one. |
It returns:
handlers |
GET, POST, and DELETE for app/auth/[[...masks]]/route.ts. |
auth() |
The person, or null when nobody is signed in. It works in a server component, a server action, or a route handler. |
requireAuth({ returnTo }, ...privileges) |
The person, after sending a signed-out visitor to sign in and checking each privilege. { returnTo } is optional. |
withMasks(handler, ...privileges) |
Guards a route handler by session or by access token, and passes { masked } to it. |
proxy |
Refreshes the session before a page renders, and sends a signed-out visitor to a path under protect to sign in. |
masks |
The Masks underneath. |