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:
{
"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:
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:
// 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:
// 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
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
authfromvoid/client - add
authtovoid.json - add a root-level
auth.tsfile
The simplest setup is no config at all. Email/password auth is enabled by default.
Example:
{
"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_IDAUTH_GITHUB_CLIENT_SECRET
Client Usage
void/client exports a preconfigured Better Auth client:
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:
import { getSession, getUser, requireAuth } from 'void/auth';getUser()returns the currentAuthUser | nullgetSession()returns{ user, session } | nullrequireAuth(c)returns the authenticated user or throws401
Example:
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:
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:
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.providersis set without"email" auth.providerscan enable any built-in Better Auth social provider- deployed apps need an auth secret via
BETTER_AUTH_SECRET - D1/SQLite uses the app
DBbinding - PostgreSQL apps (
"database": "pg") use the appHYPERDRIVEbinding
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(...):
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:
void db generateVoid 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