Skip to content

Instantly share code, notes, and snippets.

@SouravInsights
Last active February 5, 2026 20:16
Show Gist options
  • Select an option

  • Save SouravInsights/941331e94e62f975a643f1d93a904626 to your computer and use it in GitHub Desktop.

Select an option

Save SouravInsights/941331e94e62f975a643f1d93a904626 to your computer and use it in GitHub Desktop.

Better Auth Session Validation Debugging

Date: 2026-01-31
Context: Fastify + Better Auth integration - API endpoints returning 401 even with valid session tokens

Problem

After implementing Better Auth with Fastify following the official integration guide, API endpoints were returning 401 Unauthorized even with valid session tokens from the production application.

The web app worked fine. The database had the session record. But curl requests with Authorization: Bearer <token> kept failing. No obvious errors in server logs.

Attempts

Attempt 1: Checked if the session existed in the database → Session was there. Token ID matched. Still 401.

Attempt 2: Tried using the full token (with signature) in database queries → No results. Realized the database only stores the token ID (before the .), not the signature.

Attempt 3: Used Authorization: Bearer <token> header in curl → Still 401. Better Auth wasn't recognizing it.

Attempt 4: Switched to Cookie: better-auth.session_token=<token>WORKED on localhost! But failed when using production tokens.

Attempt 5: Realized production uses __Secure-better-auth.session_token → Tried that on localhost → Failed. The __Secure- prefix requires HTTPS.

Attempt 6: Used unprefixed cookie name for localhost, prefixed for production → WORKED everywhere!

Solution

Better Auth uses cookie-based sessions, not Bearer tokens.

For localhost (HTTP):

curl -H "Cookie: better-auth.session_token=<token>" http://localhost:3001/api/v1/trips

For production (HTTPS):

curl -H "Cookie: __Secure-better-auth.session_token=<token>" https://api.example.com/v1/trips

Token structure:

  • Cookie value: {tokenId}.{signature} (e.g., rBhozMGKtQ5ujZ40W94AihzEOZpG4UYH.f3wILxj3sXOeD23DTSmrwssFAXupB1R6IbaguhgoY1w=)
  • Database stores: Only {tokenId} (e.g., rBhozMGKtQ5ujZ40W94AihzEOZpG4UYH)
  • Validation: Better Auth looks up the token ID, then verifies the signature

Root Cause

Better Auth is session-based (cookie authentication), NOT a JWT/Bearer token system.

The __Secure- prefix is a browser security standard that requires HTTPS. Browsers won't send __Secure- cookies over HTTP connections. That's why localhost needs the unprefixed version.

Key Learnings

  • Better Auth ≠ Bearer tokens. It's cookie-based. Using Authorization: Bearer doesn't work.
  • __Secure- prefix requires HTTPS. Use unprefixed cookie names for local HTTP development.
  • Database only stores token ID. The signature is validated at runtime, not stored.
  • Fastify headers need conversion. Better Auth expects standard Headers objects (Fetch API), not Fastify's plain JavaScript object.

Related Files

  • apps/server/src/plugins/auth.ts - Auth plugin with req.getSession() decorator
  • apps/server/src/routes/v1/trips.ts - Example protected route

Future Reference

When testing authenticated endpoints:

  1. Always use cookie-based auth, not Bearer tokens
  2. Use better-auth.session_token for localhost
  3. Use __Secure-better-auth.session_token for production
  4. Test the /api/auth/get-session endpoint first to verify auth is working
  5. Check the database for the token ID (before the .), not the full token
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment