Overview
iStory supports two authentication methods:
Wallet Authentication - Sign a message with your Web3 wallet
Google OAuth - Sign in with Google via Supabase Auth
Both methods return a Bearer token that must be included in all API requests.
Authentication Flow
Wallet Authentication
Request Nonce
Client requests a one-time nonce from the server
Sign Message
User signs the message containing the nonce with their wallet
Verify Signature
Server verifies the signature and nonce
Return JWT
Server returns a custom JWT token
Google OAuth
OAuth Redirect
User clicks “Sign in with Google”
Callback
Google redirects to /api/auth/callback
Session Created
Supabase creates session and returns JWT
Get Nonce
Generate a server-side nonce for wallet signature verification.
Query Parameters
Ethereum wallet address (checksummed or lowercase)
Response
Full message to sign, including nonce and timestamp
UUID v4 nonce for replay prevention
Example Request
curl "https://istory.vercel.app/api/auth/nonce?address=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
Example Response
{
"message" : "Welcome to EStory \n\n Sign this message to log in securely. \n\n Site: eStory \n Address: 0x742d35cc6634c0532925a3b844bc9e7595f0beb \n\n No transaction · No gas fees · Completely free \n\n Nonce: 550e8400-e29b-41d4-a716-446655440000 \n Timestamp: 2026-03-04T10:30:00.000Z \n Expires: 2026-03-04T10:35:00.000Z" ,
"nonce" : "550e8400-e29b-41d4-a716-446655440000"
}
Nonces expire after 5 minutes and can only be used once.
Wallet Login
Verify wallet signature and return authentication token.
Request Body
Hexadecimal signature (0x-prefixed)
The exact message that was signed (from /api/auth/nonce)
Response
Always true on successful authentication
User profile object Ethereum address (lowercase)
"wallet", "google", or "both"
Whether user has completed onboarding
JWT token for API authentication (use as Bearer token)
Example Request
curl -X POST https://istory.vercel.app/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"signature": "0xabcdef1234567890...",
"message": "Welcome to EStory...\nNonce: 550e8400-e29b-41d4-a716-446655440000..."
}'
Example Response
{
"success" : true ,
"user" : {
"id" : "123e4567-e89b-12d3-a456-426614174000" ,
"wallet_address" : "0x742d35cc6634c0532925a3b844bc9e7595f0beb" ,
"auth_provider" : "wallet" ,
"is_onboarded" : false
},
"wallet_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Error Responses
{
"error" : "Missing address, signature, or message."
}
Show 401 Unauthorized - Invalid Signature
{
"error" : "Signature verification failed."
}
Show 401 Unauthorized - Invalid Nonce
{
"error" : "Invalid or expired nonce"
}
Possible nonce errors:
Nonce not found (never requested)
Nonce already used (replay attack prevention)
Nonce expired (>5 minutes old)
Using the Token
Once authenticated, include the token in all API requests:
const response = await fetch ( 'https://istory.vercel.app/api/stories' , {
headers: {
'Authorization' : `Bearer ${ wallet_token } `
}
});
Token Validation
The server validates tokens using two methods:
Supabase JWT (Google OAuth users)
Validated via supabase.auth.getUser(token)
Returns user.id from Supabase Auth
Custom Wallet JWT (wallet-authenticated users)
Validated via verifyWalletToken(token) from /lib/jwt.ts
Returns userId from JWT payload
Tokens are validated on every request. Invalid or expired tokens return 401 Unauthorized.
Security Best Practices
Nonce Replay Prevention Each nonce can only be used once and expires after 5 minutes
No Gas Fees Signature verification is off-chain and completely free
Message Integrity The signed message includes timestamp and expiry for audit trail
Wallet Ownership Signature proves wallet ownership without revealing private keys
Implementation Notes
Nonce Storage
Nonces are stored in-memory on the server with automatic cleanup:
// lib/nonce.ts
const NONCE_EXPIRY_MS = 5 * 60 * 1000 ; // 5 minutes
JWT Signing
Wallet JWTs are signed with HS256 algorithm:
// lib/jwt.ts
await SignJWT ({ userId , walletAddress })
. setProtectedHeader ({ alg: 'HS256' })
. setIssuedAt ()
. setExpirationTime ( '7d' )
. sign ( secret );
JWT tokens expire after 7 days .
Next Steps
AI Endpoints Use AI for transcription and analysis
Story Endpoints Create and manage journal entries