Skip to content
brought to you byVoidZero
Private Beta:Void is currently in Private Beta. We do not recommend using it for mission-critical production workloads yet. Please back up your data regularly.

Authentication

⚠️ Void Apps Only

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

Void uses Better Auth as the auth engine for Void apps. Void owns the conventions and wiring; Better Auth owns the auth behavior.

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

Use requireAuth in an API route handler or a page loader to gate access. It returns the authenticated user or throws a 401:

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 exact client API comes from Better Auth. Void just preconfigures the client with basePath: "/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 Void Cloud, Void auto-creates BETTER_AUTH_SECRET for auth-enabled apps if you have not set BETTER_AUTH_SECRET yourself through project secrets.

Localhost dev uses a built-in fallback secret automatically. Outside the managed Void Cloud deploy flow, set BETTER_AUTH_SECRET yourself.

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

Void manages Better Auth migrations as part of the normal Void migration flow:

  • local dev bootstraps auth tables automatically
  • deploy runs auth migrations together with app migrations
  • users do not run a separate Better Auth CLI path

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