dero-auth
Quick Start

Quick Start

Add wallet-based login to your app.

Install

bun add dero-auth

Set 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