Require sign-in on your routes
Turn on passkeys and WhatsApp codes, guard a route with auth.require, read the session, and test the signed-out and signed-in cases.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You will turn on two sign-in methods, guard one route and test it. It takes about 15 minutes. To build the sign-in page itself, see add sign-in to a page.
You need
- A MainSDK app run from the repository.
AUTH_KEYin your secrets. It signs sessions and is required in production.
1. Turn on the methods
auth:
key: secret:AUTH_KEY
methods: [passkeys, whatsapp]
passkeys:
origins: ["https://app.example.com"]2. Guard a route
import { auth, problem } from "mainsdk";
export async function GET(req: Request) {
try {
const session = await auth.require(req); // throws MAIN-AUTH-0001 (401) if not signed in
return Response.json({ user: session.user, aal: session.aal });
} catch (err) {
return problem(err); // RFC 9457 body with code and fix
}
}Where signing in is optional, call auth.session(req): it returns the session or null.
3. Test both cases
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp } from "@mainsdk/testing";
import { auth, problem } from "mainsdk";
// the handler under test, as in app/api/me/route.ts
async function GET(req: Request) {
try {
const session = await auth.require(req);
return Response.json({ user: session.user });
} catch (err) {
return problem(err);
}
}
it("auth AC1 refuses a request with no session and accepts one", async () => {
createTestApp({ yaml: "schema: 1\nproject: shop\nauth:\n methods: [password]\n" });
const none = await GET(new Request("http://localhost/api/me"));
assert.equal(none.status, 401);
const issued = await auth.passwords.register(new Request("http://localhost/"), {
email: "jane@x.com",
password: "correct horse battery",
});
const cookie = issued.cookies.map((c) => c.split(";")[0]).join("; ");
const ok = await GET(new Request("http://localhost/api/me", { headers: { cookie } }));
assert.equal(ok.status, 200);
});Check it worked
Without a cookie the route answers 401 with MAIN-AUTH-0001 and a fix. With a valid session it answers 200. The test passes.
Troubleshooting
- 401 on every call in production: check that
AUTH_KEYresolves and that the browser sends the session cookie. MAIN-AUTH-0084: you called a sign-in route for a methodauth.methodsdoes not list.- A passkey fails on one domain: add its origin under
auth.passkeys.origins.
See the auth interface for step-up, organisations and single sign-on.
Was this page helpful?