Open-source authentication library for Next.js, SvelteKit, Express, and other JS runtimes. Pairs with a SIWE credentials provider so wallets can sign in alongside OAuth, email, and passkey providers.
- 01Next.js App Router auth
- 02SIWE + OAuth in one session
- 03self-hosted, no-vendor-lock auth
- 04JWT or database sessions
- 05custom credentials providers
- pnpm add next-auth@beta
- pnpm add siwe viem
| Variable | Scope | Description |
|---|---|---|
| AUTH_SECRET | Server | Random 32+ byte secret used to sign and encrypt the JWT/session cookie. Generate via `openssl rand -base64 32`. Server-only. |
| AUTH_URL | Server | Canonical URL of the deployed app (e.g. `https://app.example.com`). Required in production for correct callback URLs and SIWE domain binding. |
| NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID | Client | WalletConnect/Reown project ID used by wagmi/RainbowKit to connect the wallet that will sign the SIWE message. Client-safe. |
Use Auth.js (NextAuth v5) with a custom SIWE Credentials provider. In `auth.ts`, export `NextAuth({ providers: [Credentials({ name: 'Ethereum', credentials: { message: {}, signature: {} }, async authorize(creds, req) { const siwe = new SiweMessage(JSON.parse(creds.message)); const nonce = await getCsrfToken({ req }); const result = await siwe.verify({ signature: creds.signature, nonce, domain: new URL(process.env.AUTH_URL!).host }); return result.success ? { id: siwe.address } : null; } })], session: { strategy: 'jwt' }, callbacks: { jwt({ token, user }) { if (user) token.sub = user.id; return token; } } })`. On the client wrap the tree in `<SessionProvider>`, use `wagmi`/`viem` (`useSignMessage`) to sign `new SiweMessage({...}).prepareMessage()`, then call `signIn('credentials', { message, signature, redirect: false })`. Read state via `useSession()` (`session.user.id` is the address).
- ⚑Auth.js v5 uses `getCsrfToken()` as the SIWE nonce — do not generate your own nonce or `siwe.verify` will reject the message because the CSRF cookie won't match.
- ⚑The SIWE `domain` field must equal the deployed host (`new URL(AUTH_URL).host`) — `localhost:3000` works in dev but Vercel preview URLs change per deploy and will fail unless you set `AUTH_URL` per environment.
- ⚑Auth.js v4 (`next-auth@4`) and v5 (`next-auth@beta`) have incompatible APIs — v4 uses `[...nextauth].ts` route handlers, v5 uses an `auth.ts` exporting `{ handlers, auth, signIn, signOut }`; mixing examples breaks the build.
- ⚑JWT session strategy is required if you want the address available in middleware — database sessions are not readable from edge runtimes used by Next.js middleware.
- ⚑Smart-contract wallets need ERC-1271 verification: pass a viem `publicClient` to `siwe.verify({ ..., provider })` or all Safe / smart-account sign-ins will fail with `INVALID_SIGNATURE`.
- ⚑Auth.js was renamed from NextAuth.js — most blog posts still say `next-auth` and reference v4 APIs; always cross-check with `authjs.dev` for the v5 pattern.