Appearance
Auth Model
Decision: Email-Primary over Wallet-First
The VelaPay auth model uses email as the primary identity with wallet connections as secondary. This was a strategic pivot in v1.3 (originally wallet-first in v1.0–v1.2).
Why Email-Primary
| Factor | Wallet-First | Email-Primary | Why Email Wins |
|---|---|---|---|
| Merchant familiarity | Web3-native only | Familiar to all merchants | Most merchants are traditional businesses. They expect email login. |
| Session persistence | Wallet connect per session | Persistent sessions via cookies | Merchants stay logged in across browser sessions. No re-connecting wallet every time. |
| Team access | One wallet = one account | Email + org membership | Multiple team members can access the same merchant account. |
| Security granularity | All-or-nothing wallet access | Role-based access control | Admin can manage billing without treasury access. |
| Onboarding friction | Install wallet, fund wallet, connect | Enter email, verify, done | Lower barrier to entry for non-crypto-native merchants. |
| Notification delivery | No email = no notifications | Email enables notifications | Billing alerts, payment failures, subscription changes all need email. |
| Recovery | Seed phrase = recovery | Email-based recovery | Lost wallet doesn't mean lost account. Multiple recovery paths. |
Why Wallet-First Failed for Merchants
Wallet-first auth makes sense for DeFi traders who live in Phantom. It fails for merchants because:
- Merchants are business operators, not DeFi power users. They want to log in with their work email, not their hardware wallet.
- Treasury and operations should be separate. The wallet that holds funds shouldn't be the same one used for daily dashboard access. Email auth decouples them.
- Team access requires multi-user auth. A business has multiple employees who need dashboard access. Wallets are single-user.
- Compliance requires email. KYC, invoicing, tax reporting — all require email addresses. A wallet-only auth model creates friction at every compliance touchpoint.
Better Auth Selection
Why Better Auth
| Requirement | Better Auth | Auth.js | Clerk |
|---|---|---|---|
| D1 adapter | ✅ Native | ❌ No | ❌ Not applicable |
| Drizzle adapter | ✅ Native | ❌ No | ❌ Not applicable |
| Hono middleware | ✅ First-class | ❌ Express only | ❌ SDK-based |
| Self-hosted | ✅ Yes | ✅ Yes | ❌ Hosted service |
| Email/password | ✅ Built-in | ✅ Built-in | ✅ Built-in |
| Google SSO | ✅ Built-in | ✅ Built-in | ✅ Built-in |
| TOTP 2FA | ✅ Built-in | ❌ Requires plugin | ✅ Built-in |
| Org membership | ✅ Built-in | ❌ Custom | ✅ Built-in |
| SIWS (Sign-In with Solana) | ✅ Custom provider | ❌ Not supported | ❌ Not supported |
Better Auth Architecture
┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Browser │────→│ Hono Worker │────→│ D1 Database │
│ (React SPA) │ │ (Better Auth) │ │ (sessions, │
│ │←────│ │←────│ users, orgs)│
└──────────────────┘ └──────────────────┘ └──────────────┘
│
│ verifies
▼
┌──────────────┐
│ D1 batch() │
│ for atomic │
│ operations │
└──────────────┘Key implementation details:
- Better Auth receives the D1 binding directly (
env.DB). - D1 doesn't support interactive transactions — Better Auth uses D1's
batch()API for atomicity. - Drizzle adapter co-locates auth schema with business schema in the same D1 database.
- Hono middleware provides auth context to all API routes.
Org Model
1 Org = 1 Merchant Entity
Organization (merchant entity)
├── Members (email-based users with roles)
│ ├── Owner (created the org)
│ ├── Admin (can manage members and billing)
│ └── Member (can view and manage plans/subscribers)
├── Wallets (linked Solana addresses)
│ ├── Primary treasury wallet
│ ├── Operations wallet
│ └── Backup wallet
└── Billing data (plans, subscribers, analytics)Why Org-Based
- Multi-user access: Multiple employees can access the same merchant account with different permission levels.
- Wallet flexibility: An org can link multiple wallets (treasury, operations, backup) without creating multiple accounts.
- Team management: Owner can invite team members via email, assign roles, and revoke access.
- Audit trail: Every action is attributed to a specific user within the org.
SIWS Repurposed as Wallet-Link Verification
Sign-In with Solana (SIWS) is not used for authentication. Instead, it's repurposed as wallet-link verification:
- Merchant creates account via email/password or Google SSO.
- Merchant navigates to "Link Wallet" in dashboard settings.
- Dashboard generates a SIWS challenge (nonce + domain + statement).
- Merchant signs the challenge with their wallet.
- Dashboard verifies the signature and links the wallet address to the org.
Why Not SIWS for Auth
- Single-device limitation: SIWS requires wallet access on the current device. Email auth works from any device.
- No session persistence: Wallet auth requires re-signing on session expiry. Email auth uses persistent cookies.
- No team access: SIWS binds to a single wallet. Org model requires multi-user auth.
- Recovery: Lost wallet = lost access (with SIWS). Email has recovery flows.
SIWS Message Format
typescript
const message = `${domain} wants you to sign in with your Solana account:
${publicKey}
${statement}
Nonce: ${nonce}
Issued At: ${issuedAt}
Expiration Time: ${expiresAt}`;The nonce is stored in D1 and validated server-side to prevent replay attacks.
Google SSO Integration
Google SSO is offered as an alternative to email/password for merchants who prefer it:
- Implementation: Better Auth's built-in Google provider.
- Account linking: Google SSO creates a Better Auth account with email verified automatically.
- Org membership: Google SSO users go through the same org invitation flow.
- Fallback: Email/password is always available as a fallback if Google is down or the merchant prefers not to use it.
Migration Path for Legacy Wallet-Only Users
v1.0–v1.2 used wallet-first auth. v1.3 introduced email-primary. The migration path:
- Wallet-based accounts still work: Existing wallet-linked accounts are preserved in D1.
- Email association flow: On first login after v1.3, wallet-only users are prompted to associate an email address.
- Backward compatibility: SIWS login still works for existing users who haven't migrated.
- Gradual migration: No forced migration. Wallet-only auth is deprecated but not removed.
Migration Steps
Legacy user (wallet-only)
│
▼
Dashboard detects no email associated
│
▼
Prompt: "Add email for notifications and team access"
│
▼
User enters email → verification email sent
│
▼
Email verified → org created with wallet as linked address
│
▼
User can now log in via email or walletSecurity Model
Rate Limiting
| Endpoint | Rate Limit | Strategy |
|---|---|---|
| Login attempts | 5 per minute per email | Progressive backoff |
| Magic link requests | 3 per minute per email | IP + email combination |
| SIWS challenges | 10 per minute per wallet | Nonce-based deduplication |
| API calls (authenticated) | 100 per minute per session | Token bucket |
| API calls (unauthenticated) | 20 per minute per IP | Fixed window |
Session Management
| Property | Configuration |
|---|---|
| Session duration | 7 days (configurable per org) |
| Session storage | D1 (via Better Auth Drizzle adapter) |
| Session invalidation | On password change, role change, or manual logout |
| Concurrent sessions | Allowed (up to 5 per user) |
| Idle timeout | 24 hours of inactivity |
| Cookie flags | HttpOnly, Secure, SameSite=Lax |
TOTP 2FA
Two-factor authentication using Time-based One-Time Passwords:
- Implementation: Better Auth's built-in TOTP support.
- Setup: QR code displayed in dashboard settings. User scans with authenticator app (Google Authenticator, Authy, etc.).
- Enforcement: Optional per org. Admin can require 2FA for all org members.
- Recovery: Backup codes generated during setup. Stored hashed in D1.
- Verification: Required for sensitive operations (wallet linking, org ownership transfer, billing changes).
Threat Model
| Threat | Mitigation |
|---|---|
| Credential stuffing | Rate limiting + progressive backoff |
| Session hijacking | HttpOnly + Secure cookies, session rotation |
| CSRF | SameSite=Lax + origin validation |
| Phishing | Magic link emails include user agent and IP |
| Wallet theft | 2FA for wallet operations, org-based recovery |
| Insider threat | Role-based access, audit trail, admin-only sensitive ops |
| D1 compromise | Better Auth hashes passwords with bcrypt. No plaintext credentials in D1. |