Quick Start
Add wallet-based login to your app.
Install
bun add dero-authSet a JWT Secret
export JWT_SECRET="$(openssl rand -base64 32)"Next.js App Router
Create API Routes
// app/api/auth/challenge/route.ts
import { createAuthHandlers } from "dero-auth/next";
const { challengeHandler } = createAuthHandlers({
domain: "myapp.com",
uri: "https://myapp.com",
jwtSecret: process.env.JWT_SECRET!,
});
export const POST = challengeHandler;// app/api/auth/verify/route.ts
import { createAuthHandlers } from "dero-auth/next";
const { verifyHandler } = createAuthHandlers({
domain: "myapp.com",
uri: "https://myapp.com",
jwtSecret: process.env.JWT_SECRET!,
});
export const POST = verifyHandler;Add the Provider and Button
// app/layout.tsx
import { DeroAuthProvider } from "dero-auth/react";
export default function Layout({ children }) {
return (
<DeroAuthProvider appName="My App">
{children}
</DeroAuthProvider>
);
}// app/login/page.tsx
import { SignInWithDero } from "dero-auth/react";
export default function LoginPage() {
return <SignInWithDero />;
}That's it. The button handles the full flow: wallet connection, challenge signing, and verification.
Custom Hook (Alternative)
If you don't want the provider pattern:
"use client";
import { useDeroAuth } from "dero-auth/react";
export default function LoginPage() {
const { signIn, signOut, isAuthenticated, address, isLoading } = useDeroAuth();
if (isAuthenticated) {
return (
<div>
<p>Signed in as {address}</p>
<button onClick={signOut}>Sign Out</button>
</div>
);
}
return (
<button onClick={signIn} disabled={isLoading}>
{isLoading ? "Connecting..." : "Sign In with DERO"}
</button>
);
}Production: Redis Nonce Store
For production deployments with multiple instances, use a shared nonce store:
import { createClient } from "redis";
import { createAuthHandlers } from "dero-auth/next";
import { createRedisNonceStoreFromNodeRedis } from "dero-auth/server";
const redis = createClient({ url: process.env.REDIS_URL! });
await redis.connect();
const nonceStore = createRedisNonceStoreFromNodeRedis(redis, {
keyPrefix: "myapp:auth:nonce:",
});
const { challengeHandler, verifyHandler } = createAuthHandlers({
domain: "myapp.com",
uri: "https://myapp.com",
jwtSecret: process.env.JWT_SECRET!,
nonceStore,
});The default nonce store is in-memory and works for single-instance deployments. For multi-instance setups (e.g., behind a load balancer), use Redis to ensure replay protection works across all instances.
What's Next
- Authentication Flow — Understand the full flow
- Cryptography — How Schnorr verification works on DERO
- Next.js Integration — Route protection and middleware