Nostr WoT

Connexion serveur, cookies et JWT

Guide d’intégration et référence API : Connexion serveur, cookies et JWT.

Cette référence détaillée de l’API est maintenue en anglais et suit le code SDK lié. Les exemples avec des variables applicatives nécessitent votre signataire, vos clés, votre stockage ou votre 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 });

Référence API