coldstart

Authentication

Better Auth across web, mobile, and API

Every project with an API gets Better Auth — email/password, optional OAuth, typed sessions, and auth pages on every platform.

What's generated

apps/api/src/lib/auth.ts — Better Auth config with Drizzle adapter, email/password, OAuth providers, 7-day sessions.

apps/api/src/middleware/session.ts — two middleware:

apps/api/src/middleware/session.ts
// Extracts session — sets c.get("user") and c.get("session")
export const sessionMiddleware = createMiddleware<AppEnv>(async (c, next) => {
  const session = await auth.api.getSession({
    headers: c.req.raw.headers,
  });
  c.set("user", session?.user ?? null);
  c.set("session", session?.session ?? null);
  await next();
});

// Blocks unauthenticated requests
export const requireAuth = createMiddleware<AppEnv>(async (c, next) => {
  const user = c.get("user");
  if (!user) return c.json({ error: "Unauthorized" }, 401);
  await next();
});

apps/api/src/types.ts — typed context so c.get("user") returns { id, email, name }:

apps/api/src/types.ts
export type AppEnv = {
  Variables: {
    user: { id: string; email: string; name: string } | null;
    session: { id: string; expiresAt: Date } | null;
  };
};

Three auth pages under (auth)/:

  • Sign in — email/password form + OAuth buttons (Google, GitHub, Apple)
  • Sign up — registration with name, email, password
  • Forgot password — password reset flow

All are "use client" components with loading states, error handling, and i18n-aware navigation.

src/lib/auth-client.ts — Better Auth client:

apps/web/src/lib/auth-client.ts
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient({
  baseURL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001",
});

Use authClient.useSession(), authClient.signIn.email(), authClient.signUp.email(), authClient.signOut().

Two auth screens under app/(auth)/:

  • Sign in — email/password + social buttons
  • Sign up — registration

Both use the Better Auth React Native client at lib/auth-client.ts.

apps/mobile/lib/auth-client.ts
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient({
  baseURL: process.env.EXPO_PUBLIC_API_URL || "http://localhost:3001",
});

OAuth providers

Select during coldstart init or add later:

Terminal
coldstart add auth --providers google,github,apple

Each provider needs environment variables, validated at startup:

ProviderVariables
GoogleGOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
GitHubGITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
AppleAPPLE_CLIENT_ID, APPLE_CLIENT_SECRET

OAuth buttons are automatically added to sign-in and sign-up pages on both web and mobile.

Multi-tenant & RBAC

Enable multi-tenant for organization support:

Terminal
coldstart add tenant

This generates:

  • Database tables: organization, member (with role), invitation (with token + expiry)
  • Organization middleware: extracts org from x-organization-slug header, verifies membership
  • RBAC middleware: requireRole("admin") checks role hierarchy (owner > admin > member > viewer)
Protecting org routes
app.use("/org/:orgSlug/*", organizationMiddleware);
app.use("/org/:orgSlug/settings/*", requireRole("admin"));
app.delete("/org/:orgSlug", requireRole("owner"), deleteOrg);

Per-seat billing auto-enables multi-tenant and RBAC.

On this page