Nostr WoT

Login no servidor, cookies e JWTs

Guia de integração e referência de API: Login no servidor, cookies e JWTs.

Esta referência detalhada da API é mantida em inglês e acompanha o código do SDK indicado. Os exemplos com variáveis da aplicação precisam do seu assinador, chaves, armazenamento ou interface.

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 });

Referência da API