Quickback Docs

Authentication Security

Configure secure cookies, rate limiting, and cross-domain authentication. Learn best practices for production-ready auth configuration.

Quickback enforces secure authentication defaults out of the box. This page explains how cookie security, rate limiting, and cross-domain authentication work, plus how to configure them for your use case.

Quickback sets secure cookie defaults to protect against CSRF, XSS, and man-in-the-middle attacks.

Default Attributes

auth: defineAuth("better-auth", {
  advanced: {
    defaultCookieAttributes: {
      sameSite: 'lax',   // CSRF protection
      secure: true,       // HTTPS only
      httpOnly: true,     // No JavaScript access
    }
  }
})

What each attribute does:

AttributeValuePurpose
sameSitelaxBlocks cookies on cross-site POST requests (CSRF protection)
securetrueCookies only sent over HTTPS (prevents interception)
httpOnlytrueJavaScript cannot access cookies via document.cookie (XSS mitigation)

SameSite Options

  • lax (recommended): Blocks cross-site POST requests but allows top-level navigation. Best balance of security and usability.
  • strict: Blocks cookies on all cross-site requests. More secure but may break legitimate flows (e.g., returning from payment provider).
  • none: Allows all cross-site requests. Required for cross-domain authentication (requires secure: true).

Development Override

For local development over HTTP, you can relax the secure flag:

auth: defineAuth("better-auth", {
  advanced: {
    defaultCookieAttributes: {
      sameSite: 'lax',
      secure: process.env.NODE_ENV === 'production',
      httpOnly: true,
    }
  }
})

Warning: Never use secure: false in production.


Rate Limiting

Quickback includes per-IP rate limiting by default to protect against brute-force attacks and abuse.

How It Works

Rate limits are applied per IP address, not globally:

┌─────────────────────────────────────────────────┐
│ IP: 192.168.1.1                                 │
│ Endpoint: /sign-in/email                        │
│ Limit: 10 requests per 60 seconds              │
│                                                 │
│ Request 1-10: ✅ Allowed                       │
│ Request 11: ❌ 429 Too Many Requests           │
│ After 60s: Counter resets to 0                 │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│ IP: 192.168.1.2 (different IP)                 │
│ Endpoint: /sign-in/email                        │
│ Independent counter: Not affected by 1.1        │
│ Starts at 0                                     │
└─────────────────────────────────────────────────┘

Default Configuration

auth: defineAuth("better-auth", {
  rateLimit: {
    enabled: true,
    window: 60,        // 60 second window
    max: 100,          // 100 requests per IP per window
    customRules: {
      "/sign-in/email": { window: 60, max: 10 },
      "/sign-in/social": { window: 60, max: 10 },
      "/device/init": { window: 60, max: 5 },
      "/device/poll": { window: 60, max: 30 },
      "/device/token": { window: 60, max: 10 },
    }
  }
})

Custom Rules by Endpoint

Set different limits based on endpoint sensitivity:

Endpoint TypeRecommended LimitReasoning
Sign-in10/min per IPPrevent credential stuffing
Device codes5/min per IPPrevent device code enumeration
Token exchange10/min per IPPrevent token brute-force
Polling30/min per IPAllow reasonable polling (every 2s)
General API100/min per IPBalance abuse prevention and usability

Why Per-IP?

Per-IP (what Quickback uses):

  • Prevents single attacker from brute-forcing
  • Doesn't penalize legitimate users when one IP is malicious
  • Standard for auth endpoints

Global total (not used):

  • Would allow distributed attacks to succeed
  • One attacker with many IPs could exhaust the limit for everyone
  • Not effective for security

Disabling Rate Limiting

Not recommended, but you can disable for testing:

rateLimit: {
  enabled: process.env.NODE_ENV === 'production',
  window: 60,
  max: 1000,  // Higher limit for development
}

Cross-Domain Authentication

For applications spanning multiple subdomains (e.g., account.example.com, api.example.com, dashboard.example.com), Quickback provides dual-layer security.

The Problem

Browsers restrict cookies to exact domains by default. If your auth API is at api.example.com, cookies won't be sent to dashboard.example.com.

The Solution: Dual-Layer Protection

Quickback uses two complementary security layers:

auth: defineAuth("better-auth", {
  advanced: {
    crossSubDomainCookies: {
      enabled: true,
      domain: 'example.com'  // All *.example.com can access cookies
    }
  }
})

Behavior: Allows ALL subdomains to access the cookie (browser limitation per RFC 6265)

  • Cannot selectively allow only specific subdomains
  • All-or-nothing: either exact domain only, or all subdomains

2. Trusted Origins (Application-Level)

auth: defineAuth("better-auth", {
  trustedOrigins: [
    'https://account.example.com',
    'https://api.example.com',
    'https://dashboard.example.com'
  ]
})

Behavior: Better Auth validates request origins before processing auth operations

  • Granular whitelist of allowed origins
  • Blocks requests from untrusted subdomains even if they have the cookie
  • Prevents CSRF and unauthorized auth operations

Security Model

┌─────────────────────────────────────────────────┐
│ Subdomain: evil.example.com                     │
│ Has Cookie: ✓ (via crossSubDomainCookies)      │
│ In trustedOrigins: ✗                           │
│ Result: REQUEST BLOCKED by Better Auth         │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│ Subdomain: account.example.com                  │
│ Has Cookie: ✓ (via crossSubDomainCookies)      │
│ In trustedOrigins: ✓                           │
│ Result: REQUEST ALLOWED                        │
└─────────────────────────────────────────────────┘

Full Cross-Domain Configuration

import { defineAuth, defineConfig, defineDatabase, defineRuntime } from "@quickback/compiler";

export default defineConfig({
  name: "my-saas",
  providers: {
    runtime: defineRuntime("cloudflare"),
    database: defineDatabase("cloudflare-d1", {
      vars: {
        ACCOUNT_URL: "https://account.example.com",
        BETTER_AUTH_URL: "https://api.example.com",
      }
    }),
    auth: defineAuth("better-auth", {
      emailAndPassword: { enabled: true },
      plugins: ["organization"],

      // Trusted origins (granular whitelist)
      trustedOrigins: [
        "https://account.example.com",
        "https://api.example.com",
        "https://dashboard.example.com",
        "http://localhost:3000",  // Development
      ],

      // Cookie configuration
      advanced: {
        crossSubDomainCookies: {
          enabled: true,
          domain: "example.com",
        },
        defaultCookieAttributes: {
          secure: true,
          sameSite: "none",  // Required for cross-domain
          httpOnly: true,
        }
      }
    })
  }
})

Important: When using sameSite: "none", you must also set secure: true. This is required by browsers.


Environment Variables

Set these in your Cloudflare Workers environment or .env file:

Required

# Better Auth secret (minimum 32 characters)
BETTER_AUTH_SECRET=your-secret-key-min-32-chars

# Base URL for Better Auth endpoints
BETTER_AUTH_URL=https://api.example.com

Optional (Cross-Domain)

# Enable cross-subdomain cookies
CROSS_SUBDOMAIN_COOKIES=true
COOKIE_DOMAIN=example.com

# Trusted origins (comma-separated)
TRUSTED_ORIGINS=https://account.example.com,https://api.example.com

# CORS allowed origins (typically matches trustedOrigins)
ALLOWED_ORIGINS=https://account.example.com,https://api.example.com

Setting Secrets with Wrangler

For sensitive values like BETTER_AUTH_SECRET:

wrangler secret put BETTER_AUTH_SECRET
# Paste your secret when prompted

For non-secret vars, add to wrangler.toml:

[vars]
BETTER_AUTH_URL = "https://api.example.com"
CROSS_SUBDOMAIN_COOKIES = "false"

Security Best Practices

For most applications, use exact domain cookies:

auth: defineAuth("better-auth", {
  trustedOrigins: ["https://app.example.com"],
  advanced: {
    crossSubDomainCookies: {
      enabled: false  // Default: exact domain only
    },
    defaultCookieAttributes: {
      sameSite: 'lax',
      secure: true,
      httpOnly: true,
    }
  }
})

Benefits:

  • Maximum security (smallest cookie scope)
  • No cross-subdomain attack surface
  • Simpler configuration

Multi-Subdomain (When Needed)

Only enable cross-subdomain cookies if you need SSO across multiple subdomains:

auth: defineAuth("better-auth", {
  trustedOrigins: [
    "https://account.example.com",
    "https://dashboard.example.com",
  ],
  advanced: {
    crossSubDomainCookies: {
      enabled: true,
      domain: "example.com",
    },
    defaultCookieAttributes: {
      sameSite: 'none',  // Required for cross-domain
      secure: true,
      httpOnly: true,
    }
  }
})

Security requirements:

  • Trust all subdomains equally (any compromised subdomain can steal cookies)
  • Use separate root domains for untrusted services
  • Always set trustedOrigins for granular control

CORS vs Trusted Origins

These serve different purposes and should typically match:

SettingPurpose
ALLOWED_ORIGINS (CORS)Controls which origins can make credentialed requests
trustedOrigins (Better Auth)Validates origins for auth operations + prevents open redirects

Example:

database: defineDatabase("cloudflare-d1", {
  vars: {
    ALLOWED_ORIGINS: "https://account.example.com,https://api.example.com"
  }
}),
auth: defineAuth("better-auth", {
  trustedOrigins: [
    "https://account.example.com",
    "https://api.example.com"
  ]
})

Checklist

Before deploying to production:

  • BETTER_AUTH_SECRET is set (minimum 32 random characters)
  • secure: true is enabled (HTTPS only)
  • httpOnly: true is enabled (XSS protection)
  • sameSite: 'lax' or 'strict' unless cross-domain required
  • trustedOrigins includes only your domains
  • ALLOWED_ORIGINS matches trustedOrigins
  • Rate limiting is enabled (rateLimit.enabled: true)
  • Custom rate limits set for sensitive endpoints

Deployment Considerations

Behind Proxy/Load Balancer

If your app is behind a proxy, ensure IP address headers are trusted:

auth: defineAuth("better-auth", {
  autoDetectIpAddress: true,  // Uses X-Forwarded-For or CF-Connecting-IP
})

Cloudflare Workers: IP automatically available via CF-Connecting-IP header

Other proxies: Configure to trust X-Forwarded-For or X-Real-IP

Multiple Environments

Use environment variables to configure different settings per environment:

auth: defineAuth("better-auth", {
  trustedOrigins: process.env.TRUSTED_ORIGINS?.split(',') || [],
  advanced: {
    crossSubDomainCookies: {
      enabled: process.env.CROSS_SUBDOMAIN_COOKIES === 'true',
      domain: process.env.COOKIE_DOMAIN,
    },
    defaultCookieAttributes: {
      sameSite: process.env.COOKIE_SAMESITE || 'lax',
      secure: process.env.NODE_ENV === 'production',
      httpOnly: true,
    }
  }
})

Development (.dev.vars):

TRUSTED_ORIGINS=http://localhost:3000,http://localhost:5173
CROSS_SUBDOMAIN_COOKIES=false
COOKIE_SAMESITE=lax

Production (Wrangler secrets):

TRUSTED_ORIGINS=https://account.example.com,https://api.example.com
CROSS_SUBDOMAIN_COOKIES=true
COOKIE_DOMAIN=example.com
COOKIE_SAMESITE=none

Further Reading

On this page