Learn how to handle authentication flows with Privy and Base Account, including both Privy-managed authentication and custom backend verification.
Overview
Privy handles the initial authentication flow, managing user sessions and wallet connections. You can also implement additional authentication layers for enhanced security or custom requirements.
The code snippets in this guide are based on the following example project:
Base Account Privy Template https://github.com/base/base-account-privy
Authentication Flow
Privy manages the primary authentication before users enter your application:
Custom Authentication
For additional security or custom authentication requirements, you can implement backend verification using Sign-In with Ethereum (SIWE)
with the Base Account SDK.
Setup
Follow the Setup guide to set up Privy with Base Account.
Frontend Component (Sign In With Base)
We use the SignInWithBaseButton
component from the @base-org/account-ui/react
package to make sure
we are following the brand guidelines.
Authentication Component (components/sections/authentication.tsx)
"use client" ;
import { useState } from "react" ;
import { useBaseAccountSdk } from "@privy-io/react-auth" ;
import { SignInWithBaseButton } from "@base-org/account-ui/react" ;
export const Authentication = () => {
const { baseAccountSdk } = useBaseAccountSdk ();
const [ loading , setLoading ] = useState ( false );
const [ verificationResult , setVerificationResult ] = useState < any >( null );
const provider = baseAccountSdk ?. getProvider ();
const handleSignInWithBase = async () => {
if ( ! provider ) return ;
try {
setLoading ( true );
// Get a fresh nonce from backend
const nonceResponse = await fetch ( "/api/auth/nonce" );
const { nonce } = await nonceResponse . json ();
// Switch to Base Chain
await provider . request ({
method: "wallet_switchEthereumChain" ,
params: [{ chainId: "0x2105" }],
});
// Connect and authenticate with SIWE
const response = ( await provider . request ({
method: "wallet_connect" ,
params: [{
version: "1" ,
capabilities: {
signInWithEthereum: {
nonce ,
chainId: "0x2105" ,
},
},
}],
})) as {
accounts : {
address : string ;
capabilities : {
signInWithEthereum : { signature : string ; message : string };
};
}[];
};
const { address } = response . accounts [ 0 ];
const { message , signature } = response . accounts [ 0 ]. capabilities . signInWithEthereum ;
// Verify with backend
const verifyResponse = await fetch ( "/api/auth/verify" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({ address , message , signature }),
});
const result = await verifyResponse . json ();
setVerificationResult ( result );
} catch ( error ) {
console . error ( "Sign in error:" , error );
} finally {
setLoading ( false );
}
};
return (
< div >
< SignInWithBaseButton onClick = { handleSignInWithBase } />
{ verificationResult && (
< div > ✅ Backend Verified! Address: { verificationResult . address } </ div >
) }
</ div >
);
};
export default Authentication ;
See all 80 lines
Using the Authentication Component
Add the Authentication component to your page to enable Sign In with Base functionality:
Page Implementation (app/page.tsx)
Alternative: Protected Page (app/dashboard/page.tsx)
import Authentication from "@/components/sections/authentication" ;
export default function Home () {
return (
< main className = "flex min-h-screen flex-col items-center justify-center p-24" >
< div className = "z-10 w-full max-w-5xl items-center justify-between font-mono text-sm" >
< h1 className = "text-4xl font-bold text-center mb-8" >
Base Account with Privy
</ h1 >
< div className = "flex flex-col items-center space-y-4" >
< Authentication />
</ div >
</ div >
</ main >
);
}
Backend Implementation
Development Only : This backend implementation is not production-ready. The nonce management system needs proper persistence and security enhancements for production use.
Nonce Generation (app/api/auth/nonce/route.ts)
Signature Verification (app/api/auth/verify/route.ts)
Nonce Store (lib/nonce-store.ts)
import { NextResponse } from 'next/server' ;
import crypto from 'crypto' ;
import { nonceStore } from '@/lib/nonce-store' ;
export async function GET () {
try {
const nonce = crypto . randomBytes ( 16 ). toString ( 'hex' );
nonceStore . add ( nonce );
return NextResponse . json ({ nonce });
} catch ( error ) {
return NextResponse . json (
{ error: 'Failed to generate nonce' },
{ status: 500 }
);
}
}
Production Considerations
For production deployments, enhance the backend implementation with:
Persistent storage : Use Redis or a database instead of in-memory storage
Rate limiting : Implement request rate limiting for nonce generation
Session management : Create proper JWT tokens or session cookies
Nonce expiration : Add timestamp-based nonce expiration