Skip to content
brought to you byVoidZero

Authentication ​

⚠️ Void Apps Only

Void-managed auth currently works only for Void apps. Meta-framework mode is not supported yet.

Void configures Better Auth for your app, including its database connection, API routes, and client. Start with email and password, or add a social login provider.

Quick Start ​

1. Enable auth ​

Add a provider to void.json. Email/password is the default, so the simplest config is:

json
{
  "auth": {
    "providers": ["email"]
  }
}

You can also skip this step entirely. Auth activates automatically when you import from void/auth or void/client.

2. Sign up and sign in ​

Use the preconfigured auth client from void/client:

tsx
import { auth } from 'void/client';

// sign up
await auth.signUp.email({
  email: 'alice@example.com',
  password: 's3cret',
  name: 'Alice',
});

// sign in
await auth.signIn.email({
  email: 'alice@example.com',
  password: 's3cret',
});

3. Protect a server route ​

Call requireAuth in a route handler or page loader that needs a signed-in user. It returns that user, or throws 401 if the request isn't authenticated:

ts
// routes/api/profile.ts
import { defineHandler } from 'void';
import { requireAuth } from 'void/auth';

export const GET = defineHandler((c) => {
  const user = requireAuth(c);
  return { email: user.email };
});

In a pages-mode loader, use getUser or getSession when you want to allow unauthenticated access:

ts
// pages/dashboard.server.ts
import { defineHandler, type InferProps } from 'void';
import { getUser } from 'void/auth';

export type Props = InferProps<typeof loader>;

export const loader = defineHandler(() => {
  const user = getUser();
  return { user };
});

4. Sign out ​

tsx
await auth.signOut();

The rest of this page covers configuration, server helpers, and advanced customization in detail.

Config ​

Auth turns on automatically when you:

  • import anything from void/auth
  • import auth from void/client
  • add auth to void.json
  • add a root-level auth.ts file

The simplest setup is no config at all. Email/password auth is enabled by default.

Example:

json
{
  "auth": {
    "providers": ["email", "google", "github"]
  }
}

Use auth.providers when you want to enable social providers explicitly. Provider credentials come from AUTH_<PROVIDER>_CLIENT_ID and AUTH_<PROVIDER>_CLIENT_SECRET.

See the full provider list in the config reference.

For example, github uses:

  • AUTH_GITHUB_CLIENT_ID
  • AUTH_GITHUB_CLIENT_SECRET

Client Usage ​

void/client exports a preconfigured Better Auth client:

ts
import { auth } from 'void/client';

await auth.signUp.email({
  email: 'alice@example.com',
  password: 's3cret',
  name: 'Alice',
});

await auth.signIn.email({
  email: 'alice@example.com',
  password: 's3cret',
});

await auth.signOut();

The client uses Better Auth's API, configured at /api/auth.

See the official Better Auth client docs for the full client API.

Framework-specific clients are selected automatically:

  • React pages apps use better-auth/react
  • Vue pages apps use better-auth/vue
  • Svelte pages apps use better-auth/svelte
  • Solid pages apps use better-auth/solid
  • other Void apps use better-auth/client

For advanced usage, void/client also exports createAuthClient.

Server Usage ​

void/auth provides the server-side helpers:

ts
import { getSession, getUser, requireAuth } from 'void/auth';
  • getUser() returns the current AuthUser | null
  • getSession() returns { user, session } | null
  • requireAuth(c) returns the authenticated user or throws 401

Example:

ts
import { defineHandler } from 'void';
import { requireAuth } from 'void/auth';

export const GET = defineHandler((c) => {
  const user = requireAuth(c);
  return { email: user.email };
});

AuthUser ​

AuthUser maps to Better Auth's core user type:

ts
interface AuthUser {
  id: string;
  email: string;
  emailVerified: boolean;
  name: string;
  image?: string | null;
  createdAt: Date;
  updatedAt: Date;
}

AuthSession ​

AuthSession maps to Better Auth's core session type:

ts
interface AuthSession {
  id: string;
  token: string;
  userId: string;
  expiresAt: Date;
  createdAt: Date;
  updatedAt: Date;
  ipAddress?: string | null;
  userAgent?: string | null;
}

Behavior ​

When auth is active, Void configures Better Auth with these conventions:

  • mount path: /api/auth/*
  • email/password enabled by default unless auth.providers is set without "email"
  • auth.providers can enable any built-in Better Auth social provider
  • deployed apps need an auth secret via BETTER_AUTH_SECRET
  • D1/SQLite uses the app DB binding
  • PostgreSQL apps ("database": "pg") use the app HYPERDRIVE binding

Auth sessions live in the same database system as the rest of the app. AUTH_KV is no longer used.

On both deployment targets, Void keeps an existing BETTER_AUTH_SECRET or generates one when it's missing. The value is stored as an encrypted Worker secret and reused by later versions.

Localhost development uses a built-in fallback secret automatically. Production deployment through Void manages the secret lifecycle for you.

Customization ​

For advanced configuration, create auth.ts at the project root and export defineAuth(...):

ts
import { defineAuth } from 'void/auth';

export default defineAuth(({ defaults }) => ({
  ...defaults,
  trustedOrigins: ['https://example.com'],
}));

defaults already includes Void's conventions. Extend it explicitly instead of expecting a deep merge.

For the full set of available options, see the official Better Auth options reference.

Database and Migrations ​

Auth tables live alongside your app's tables. During local development, Void creates them automatically.

A Void platform deploy creates the Better Auth tables at runtime after dispatch. A direct Cloudflare deploy requires those tables in your checked-in migrations. Generate them with:

sh
void db generate

Void adds the production Better Auth schema, including configured model names and plugin tables, to Drizzle's migration input. Review and commit the SQL before deploying. You don't need to duplicate the auth tables in db/schema.ts or run a separate Better Auth CLI.

MySQL stores OAuth access, refresh, and ID tokens as unbounded text. Existing direct-deploy MySQL apps should run void db generate once after upgrading to widen earlier varchar(255) token columns. Void platform deployments apply the same safe widening automatically.

Unsupported Modes ​

Void-managed Better Auth is supported only for Cloudflare Void apps in v1.

  • meta-framework mode should use Better Auth's official framework integrations directly
  • target: "node" | "bun" | "deno" should use Better Auth directly