Server login, cookies and JWTs
Mount login routes, issue sessions and read authenticated server claims.
This detailed API reference is maintained in English and follows the linked SDK source. Code examples with application variables need your own signer, keys, storage or UI.
Quick start (Next.js App Router)
1. Create the service
ts
// lib/auth.ts
import { createAuthService } from "@nostr-wot/auth";
import { createNextHandlers } from "@nostr-wot/auth/next";
export const auth = createAuthService({
secret: process.env.NOSTR_AUTH_SECRET!, // 32+ random bytes; HMAC + JWT
challengeTtlSec: 300, // 5 min
jwtTtlSec: 60 * 60 * 24 * 7, // 1 week
jwtIssuer: "https://myapp.com",
expectedVerifyUrl: "https://myapp.com/api/auth/verify",
// Optional: hook into a successful verify to add custom claims or
// create/lookup a user row in your DB.
async onVerify({ pubkey }) {
const user = await db.user.upsert(pubkey);
return { uid: user.id, role: user.role };
},
});
export const handlers = createNextHandlers(auth, {
cookie: true, // also set/clear the JWT as an HttpOnly cookie
cookieName: "myapp_auth", // default "nw_auth"
cookieAttrs: { sameSite: "Lax", secure: true },
});2. Mount the routes
ts
// app/api/auth/challenge/route.ts
import { handlers } from "@/lib/auth";
export const POST = handlers.challenge;ts
// app/api/auth/verify/route.ts
import { handlers } from "@/lib/auth";
export const POST = handlers.verify;ts
// app/api/auth/me/route.ts
import { handlers } from "@/lib/auth";
export const GET = handlers.me;ts
// app/api/auth/logout/route.ts
import { handlers } from "@/lib/auth";
export const POST = handlers.logout;That's it. Four files. <5min of work.
3. Drive the flow from the client
ts
import { loginWithSigner, fetchMe, logout } from "@nostr-wot/auth/client";
import { Nip07Signer } from "nostr-wot-sdk/signers";
const signer = new Nip07Signer();
const { jwt, pubkey } = await loginWithSigner({
baseUrl: "/api/auth",
signer,
});
// Cookie is now set; subsequent fetches with `credentials: 'include'` work.
const me = await fetchMe({ baseUrl: "/api/auth" });
// → { pubkey, uid, role, iat, exp, ... }
await logout({ baseUrl: "/api/auth" });Custom claims via onVerify
ts
createAuthService({
secret: process.env.NOSTR_AUTH_SECRET!,
async onVerify({ pubkey, event }) {
const user = await db.users.findUnique({ where: { pubkey } });
if (!user || user.banned) throw new Error("forbidden");
return {
uid: user.id,
role: user.role,
tier: user.tier,
};
},
});The returned object is merged into the JWT payload. Throwing aborts the login (returns 401 with reason: "hook_rejected").
Reading the JWT in server code
For RSC or server actions:
ts
// app/dashboard/page.tsx (server component)
import { handlers } from "@/lib/auth";
import { headers } from "next/headers";
export default async function Dashboard() {
const headerList = await headers();
const req = new Request("http://x", { headers: headerList });
const payload = await handlers.readJwt(req);
if (!payload) redirect("/login");
return <h1>Welcome {payload.pubkey}</h1>;
}For middleware:
ts
// middleware.ts
import { auth } from "@/lib/auth";
import { NextResponse } from "next/server";
export async function middleware(req: Request) {
const cookie = req.headers.get("cookie")?.match(/nw_auth=([^;]+)/)?.[1];
const payload = cookie ? await auth.verifyJwt(decodeURIComponent(cookie)) : null;
if (!payload) return NextResponse.redirect("/login");
}Other frameworks
createHandlers returns Web-standard (req: Request) => Promise<Response> functions. Mount in:
- Hono —
app.post("/auth/challenge", (c) => handlers.challenge(c.req.raw)) - Bun.serve —
if (url.pathname === "/auth/challenge") return handlers.challenge(req) - Cloudflare Workers — same as above
- Deno —
Deno.serve(handlers.challenge)
Lower-level primitives
For when you need to plug into a custom flow:
ts
import {
issueChallenge,
verifyChallenge,
verifyAuthEvent,
signAuthJwt,
verifyAuthJwt,
} from "@nostr-wot/auth";
const { challenge, expiresAt } = await issueChallenge(secret, 300);
const result = await verifyChallenge(challenge, secret, 300);
// → { ok, reason?, issuedAt? }
const verifyResult = await verifyAuthEvent(signedEvent, {
secret,
challengeTtlSec: 300,
expectedUrl: "https://myapp.com/api/auth/verify",
expectedMethod: "POST",
});
const jwt = await signAuthJwt({ pubkey, role: "admin" }, { secret });
const payload = await verifyAuthJwt(jwt, { secret });