SsuperslateDocs

Authentication

Configure and extend Better Auth without creating a second session authority.

Authentication

Better Auth 1.6 is the only authentication and session implementation. The pre-release schema has one canonical identity, users.id; domain and wire code may call it user_id, but there is no second stored application-user key.

Included flows

FlowContract
Email/password signup8–128 characters, Bun password hashing, verification required, no immediate auto sign-in
Email verificationsent on signup/sign-in, successful verification signs the user in
Password sign-inonly a valid verified identity receives a session
Password resetreset email through the typed mailer; successful reset revokes existing sessions
Magic link15-minute expiry, token stored hashed, successful token is single-use
Google OAuthenabled only when both server credentials exist; missing config fails closed
Account linkingexplicit linking only; implicit email-based linking is disabled
Sessionsecure Better Auth cookie, 7-day expiry, 1-day update age
Sign-outBetter Auth session revocation plus browser query/client-state clearing

Google is the only included social provider. Adding providers to match a competitor is not a release requirement.

Request path

The Hono app mounts Better Auth directly:

browser authClient
  → /auth/*
  → Better Auth handler
  → Better Auth tables in Postgres

Protected product routes use:

HttpOnly session cookie
  → sessionAuth
  → auth.api.getSession(headers)
  → c.var.user_id = session.user.id
  → ownership-scoped repository query

The browser uses credentials: 'include' for both Better Auth and the typed Hono RPC client. It does not store an access token in local storage. Do not add custom access/refresh JWTs, a second cookie, a second password implementation, or another session table.

Database ownership

dbmate owns these Better Auth-compatible tables:

  • users;
  • auth_sessions;
  • auth_accounts;
  • auth_verifications;
  • auth_rate_limits.

Better Auth uses its supported pg adapter while application repositories use bun:sql. Better Auth CLI output may be inspected to discover a schema change, but the reviewed change must become a dbmate migration. Never run a second migration authority against a buyer database.

Sessions and accounts reference users.id with cascading database deletes. The application soft-deletes the user, then removes live sessions and reusable account credentials transactionally.

Authorization contract

Authentication proves identity; it does not prove resource ownership, role, or billing entitlement.

For a private resource:

  1. mount sessionAuth;
  2. read identity only through requireUserId(c);
  3. pass that ID into service and repository methods;
  4. include the ownership predicate in the read/write SQL;
  5. use a non-disclosing 404 for absent and wrong-owner private records;
  6. apply billing entitlement separately when the feature requires it.

Do not accept a user_id, owner ID, role, or entitlement from request input. Consult the optional Add an authenticated route playbook for a maintained implementation and verification route.

Email behavior

Verification, reset, and magic-link callbacks use the typed mailer. In development/test without Resend, the complete message and action URL are logged so the flow is deterministic. In production, RESEND_API_KEY is required and the log fallback cannot start.

Treat local logs as sensitive: they contain short-lived authentication URLs. Do not send console breadcrumbs to error monitoring, publish logs, or enable the fallback in a shared/production environment.

Origins, cookies, and proxy IPs

FRONTEND_URL, optional MARKETING_SITE_URL, and CORS_ORIGIN define trusted browser origins. Hono permits credentials only for that configured set. Better Auth uses the same trusted origins.

Set TRUSTED_PROXY_PROFILE to direct, railway, dokploy, aws-alb, gcp-cloud-run, or azure-container-apps. The Bun boundary ignores forwarding headers in direct mode. For managed ingress profiles it overwrites the internal client-IP header from the provider's expected hop shape. Prevent direct access around the selected ingress.

Better Auth and application endpoint rate limits are database-backed. Atomic fixed-window updates share each application budget across replicas.

Account deletion and billing

Deleting an application account cannot silently cancel a remote provider subscription. The server therefore returns 409 CONFLICT while the latest local subscription is active, trialing, past due, or canceled with paid time remaining.

The buyer must cancel through the selected provider's Billing portal and wait until the paid period ends. Only then can the application soft-delete the user and remove sessions/accounts in one transaction. Deletion is a terminal authentication state: Better Auth rejects every later session creation for that user, and a database trigger closes the status-check/session-insert race. A fresh magic link therefore cannot restore a deleted account. Product-specific data retention or immediate statutory erasure requirements may require a separate reviewed cancellation-and-deletion workflow before launch.

Verification

The real Postgres HTTP suite proves:

  • password signup, verification, authenticated session resolution, and logout;
  • password reset and revocation of existing sessions;
  • magic-link signup and single-use replay rejection;
  • a fresh magic link cannot recreate a deleted user's session;
  • optional Google behavior fails closed when credentials are absent;
  • unauthenticated requests return 401;
  • cross-owner private records use non-disclosing 404 behavior;
  • account deletion cannot orphan an active provider subscription.

Run:

RUN_DB_INTEGRATION_TESTS=1 \
DATABASE_URL=postgresql://app:app@localhost:5432/app?sslmode=disable \
bun test --cwd apps/server

Auth changes require this suite plus a production-like browser test of cookies, redirects, email delivery, and exact deployed origins. Unit tests alone are insufficient.

Common failures

FailureInspect
Sign-in loops to loginweb/API origins, cookie, credentials: include, and Better Auth base URL
Verification/reset email absentlocal server log or Resend sender/domain/provider error
Magic link already invalidexpiry, exact origin, or expected single-use replay protection
Google button/action failsboth Google credentials and SERVER_URL/auth/callback/google registration
Requests all share one rate bucketselected TRUSTED_PROXY_PROFILE and provider forwarding shape
Delete account returns 409cancel Billing, then wait for the recorded paid period to end

Do not weaken verification, cookie policy, trusted origins, account-linking policy, proxy trust, or rate limits to make these failures disappear.

On this page