Troubleshooting & Production Checklist

Common Issues

401 Unauthorized

Symptom: API calls return 401, authenticate() returns { valid: false }.

CauseFix
Expired access tokenCall refresh() then retry
Invalid/wrong API keyCheck REAUTH_API_KEY matches dashboard
Wrong domainVerify REAUTH_DOMAIN matches your verified domain exactly
Missing cookieEnsure credentials: 'include' on fetch calls
CORS blocking cookiesCheck your API and auth are on the same parent domain

Debug token issues server-side:

const result = await reauth.authenticate(request);
if (!result.valid) {
  console.log('Auth error:', result.error);
  // Common errors:
  // "No token provided" — no Authorization header or cookie
  // "Domain mismatch" — token was issued for a different domain
  // "Missing domain_id in token" — malformed JWT
  // "\"exp\" claim timestamp check failed" — token expired
}

OAuth Redirect Issues

Symptom: OAuth flow fails or redirects to wrong page.

CauseFix
Provider callback URL wrongAdd https://reauth.yourdomain.com/callback/google (or /callback/twitter) to your OAuth provider's allowed redirect URIs
Dashboard redirect URL wrongCheck redirect URL in reauth dashboard matches your app URL
Missing OAuth credentialsCheck Client ID/Secret in reauth dashboard
OAUTH_RETRY_EXPIRED errorOAuth retry window expired — restart the flow
import { requiresOAuthRestart } from '@reauth-dev/sdk';

if (session.error_code && requiresOAuthRestart({ code: session.error_code })) {
  // Headless: restart OAuth and redirect
  const { authUrl } = await reauth.startGoogleOAuth();
  window.location.href = authUrl;

  // Or if using hosted UI, just redirect to login
  // reauth.login();
}

Cookie Issues

Symptom: Session works in dev but not production, or cross-domain requests fail.

CauseFix
Not HTTPSCookies are Secure — must use HTTPS in production
Different domainCookies scoped to your domain; API must be same-origin or subdomain
SameSite blockingCookies are SameSite=Lax — works for top-level navigation but not cross-origin XHR without same-site
Browser privacy modeSome browsers block third-party cookies; use Bearer tokens instead

Alternative: Use Bearer tokens instead of cookies:

// Client: get a token
const tokenResponse = await reauth.getToken();
if (!tokenResponse) {
  // User is not authenticated — redirect to login
  reauth.login();
  return;
}

// Send as Authorization header
fetch('https://api.yourdomain.com/data', {
  headers: { Authorization: `Bearer ${tokenResponse.accessToken}` },
});

// Server: verify from header (no cookie needed)
const result = await reauth.authenticate({
  headers: { authorization: req.headers.authorization },
});

Webhook Signature Failures

Symptom: WebhookVerificationError thrown.

ErrorCauseFix
"Missing webhook signature header"Header not forwardedEnsure proxy/load balancer forwards reauth-webhook-signature
"Invalid signature header format"Malformed headerCheck header value matches t=<ts>,v1=<sig> format
"Webhook timestamp too old"Clock drift or delayed deliveryIncrease tolerance or check server clock sync
"Webhook signature verification failed"Wrong secret or body modifiedVerify webhook secret, use raw body (not parsed JSON)

Common middleware mistake:

// WRONG — express.json() parses the body, changing it
app.post('/webhooks', express.json(), handler);

// CORRECT — express.raw() preserves the raw body for verification
app.post('/webhooks', express.raw({ type: 'application/json' }), handler);

DNS Verification Stuck

Symptom: Domain verification doesn't complete.

CauseFix
Wrong DNS record typeCNAME for reauth.yourdomain.com, TXT for _reauth.yourdomain.com
DNS propagation delayWait 5–30 minutes
Cloudflare proxy enabledSet CNAME to DNS-only (gray cloud), not proxied (orange cloud)
Conflicting recordsRemove any existing A/AAAA records for reauth.yourdomain.com

Subscription Not Updating

Symptom: User subscribed but session.subscription.status still shows "none".

CauseFix
JWT not refreshedCall refresh() to get new JWT with updated subscription
Wrong org contextSubscription is org-scoped; check active_org_id matches
Stripe webhook delayWait a few seconds for Stripe → reauth webhook propagation

Production Checklist

Authentication

  • DNS records verified (CNAME + TXT)
  • Redirect URLs configured (no localhost)
  • OAuth callback URLs added to Google/Twitter console
  • HTTPS enabled on all endpoints

Server SDK

  • REAUTH_API_KEY stored securely (env var or secret manager)
  • REAUTH_DOMAIN set correctly
  • API key is not exposed in client-side code

Billing

  • Stripe connected in live mode (not test mode)
  • Plans configured with correct pricing
  • Checkout success/cancel URLs point to production URLs
  • Webhook endpoint added for payment events

Webhooks

  • Endpoint URL uses HTTPS
  • Webhook secret stored securely
  • Signature verification enabled (never skip in production)
  • Using raw body for signature verification (not parsed JSON)
  • Handler returns 200 quickly (async processing for slow operations)
  • Idempotent event processing (use event.id to deduplicate)

Credits

  • Idempotency keys (requestUuid) used for all charge/deposit calls
  • Insufficient balance errors handled gracefully (402 responses)
  • Refund logic for failed operations after successful charge

Organizations

  • Tested org switching + subscription scoping
  • Deletion guards tested (sole owner with members)

General

  • Error handling for all SDK calls (try/catch)
  • Token refresh flow tested (expired access token → refresh → retry)
  • Tested from incognito/private browsing (no stale cookies)
  • Rate limiting awareness (check response status codes)