Diffusal

Authentication

SIWE authentication and session management

The API uses SIWE (Sign-In with Ethereum) for wallet-based authentication. Sessions are managed via secure cookies.


Overview

FRONTEND                    WALLET                  API SERVER
  │                           │                        │
  ├── 1. Request nonce ───────────────────────────────►│
  │                           │                        │
  │◄── 2. Return nonce ────────────────────────────────┤
  │                           │                        │
  ├── 3. Create SIWE message ─►│                       │
  │                           │                        │
  │◄── 4. User signs ─────────┤                        │
  │                           │                        │
  ├── 5. Verify signature ────────────────────────────►│
  │                           │    Server verifies     │
  │                           │    and creates session │
  │◄── 6. Session cookie ──────────────────────────────┤
  │                           │                        │
  ├── 7. Authenticated requests ──────────────────────►│
  │      (Protected endpoints)                         │

SIWE Message Format

SIWE messages follow EIP-4361:

diffusal.xyz wants you to sign in with your Ethereum account:
0x1234...abcd

Sign in to Diffusal

URI: https://api.diffusal.xyz
Version: 1
Chain ID: 1
Nonce: k8j3h2g1f0e9d8c7b6a5
Issued At: 2024-01-15T12:00:00.000Z
Expiration Time: 2024-01-15T12:15:00.000Z

Message Fields

FieldDescription
domainThe domain requesting authentication
addressUser's Ethereum address
statementHuman-readable sign-in statement
uriAPI server URI
versionSIWE version (always "1")
chainIdEthereum chain ID
nonceServer-generated random nonce
issuedAtMessage creation timestamp
expirationTimeMessage expiry (15 minutes)

Authentication Flow

Step 1: Request Nonce

GET /api/auth/siwe/nonce?walletAddress=0x1234...abcd

Response:

FieldTypeDescription
noncestring32-character random nonce

Notes:

  • Nonce is valid for 15 minutes
  • Each nonce can only be used once
  • Request a new nonce for each sign-in attempt

Step 2: Create and Sign Message

The frontend creates a SIWE message with the nonce and prompts the user to sign.

Message Parameters:

ParameterValue
domaindiffusal.xyz
addressUser's connected wallet address
statement"Sign in to Diffusal"
urihttps://api.diffusal.xyz
version"1"
chainIdConnected chain ID
nonceNonce from Step 1
issuedAtCurrent ISO timestamp
expirationTime15 minutes from now

Domain Configuration

Important: The domain and uri fields serve different purposes:

FieldPurposeExample
domainFrontend origin where sign-in is initiateddiffusal.xyz
uriAPI server endpointhttps://api.diffusal.xyz

By Environment:

Environmentdomainuri
Productiondiffusal.xyzhttps://api.diffusal.xyz
Local Devlocalhosthttp://localhost:8080

Common Mistake: Using api.diffusal.xyz as the domain. The domain should match your frontend's origin, not the API server.

// Correct for production frontend at diffusal.xyz
const message = createSiweMessage({
  domain: "diffusal.xyz", // Frontend domain
  uri: "https://api.diffusal.xyz", // API endpoint
  // ...
});

// Correct for local development (frontend at localhost:3000)
const message = createSiweMessage({
  domain: "localhost",
  uri: "http://localhost:8080",
  // ...
});

The server validates that the domain matches one of the trusted origins configured in Better Auth.

Step 3: Verify Signature

POST /api/auth/siwe/verify
Content-Type: application/json

{
  "message": "diffusal.xyz wants you to sign in...",
  "signature": "0x..."
}

Request Body:

FieldTypeDescription
messagestringComplete SIWE message
signaturestringWallet signature

Response (Success):

FieldTypeDescription
userobjectUser profile
sessionobjectSession details

Response Headers:

Set-Cookie: session=eyJhbGc...; HttpOnly; Secure; SameSite=Strict; Path=/

Step 4: Use Authenticated Endpoints

Include the session cookie in subsequent requests:

GET /account/me
Cookie: session=eyJhbGc...

Or use the Authorization header:

GET /account/me
Authorization: Bearer eyJhbGc...

Session Management

Session Properties

PropertyValue
Duration7 days
StorageServer-side (database)
Token TypeJWT (in cookie)
Update Age24 hours (refreshed daily)
Cookie Cache5 minutes (for performance)

Check Session

GET /api/auth/session

Response (Authenticated):

FieldTypeDescription
user.idstringUser ID
user.addressstringWallet address
user.namestringENS name (if available)
session.idstringSession ID
session.expiresAtstringExpiry timestamp

Response (Not Authenticated):

FieldTypeDescription
usernullNo user
sessionnullNo session

Sign Out

POST /api/auth/sign-out

Effects:

  • Invalidates current session
  • Clears session cookie
  • User must re-authenticate

Security Considerations

Nonce Security

PropertyImplementation
GenerationCryptographically random
Length32 characters
Lifetime15 minutes
Single-useInvalidated after verification
AttributeValuePurpose
HttpOnlytruePrevent XSS access
SecuretrueHTTPS only
SameSiteStrictPrevent CSRF
Path/All routes

Rate Limits

EndpointLimitWindow
/api/auth/siwe/nonce101 minute
/api/auth/siwe/verify51 minute
/api/auth/sign-out101 minute

Common Attacks Prevented

AttackPrevention
Replay AttackSingle-use nonces
CSRFSameSite cookies
XSS Token TheftHttpOnly cookies
Session FixationNew session on auth
Brute ForceRate limiting

Error Handling

Verification Errors

Error CodeDescriptionSolution
invalid_nonceNonce expired or usedRequest new nonce
invalid_signatureSignature doesn't matchRe-sign message
address_mismatchSigned with wrong walletUse correct wallet
expired_messageMessage past expirationCreate new message
invalid_domainDomain doesn't matchCheck SIWE config

Session Errors

HTTP StatusDescriptionSolution
401No valid sessionRe-authenticate
403Session expiredRe-authenticate

Multiple Wallets

Users can link multiple wallets to a single account:

Check Linked Wallets

GET /auth/api/wallets

Response:

FieldTypeDescription
walletsarrayList of wallet info
totalnumberTotal wallet count

Wallet Object:

FieldTypeDescription
addressstringEthereum wallet address
isPrimarybooleanTrue if primary wallet
linkedAtstringISO timestamp when linked

Get Primary Wallet

GET /auth/api/wallets/primary

Response:

FieldTypeDescription
addressstringPrimary wallet address
userIdstringUser identifier

Verify Wallet Ownership

Verify a wallet address using SIWE signature:

POST /auth/api/wallets/verify
{
  "message": "...",
  "signature": "...",
  "address": "0x..."
}

ENS Integration

ENS names are resolved during authentication:

FieldSource
user.nameENS primary name
user.avatarENS avatar record

ENS lookups are optional and non-blocking. If ENS resolution fails, the wallet address is used as the display name.


WebSocket Authentication

WebSocket connections use the same JWT token:

1. Connect to ws://api.diffusal.xyz/ws

2. Send auth message:
   {
     "type": "auth",
     "token": "eyJhbGc..."
   }

3. Receive confirmation:
   {
     "type": "auth_success",
     "address": "0x..."
   }

See WebSocket API for details.


On this page