Authentication
Configure Better-Auth with email/password, Google OAuth, magic link, and team management.
What Ships by Default
Authentication is configured in lib/auth.ts using Better-Auth with the Drizzle adapter. The following are pre-configured:
- Email + password with required email verification
- Google OAuth (
select_accountprompt) - Magic link — passwordless email login
- Email OTP — one-time passcode verification
- Organization plugin — mapped to your
teamstable - Admin plugin — RBAC with three app-level roles
Auto Team Creation
When a user signs up (any method), a team is created automatically:
// lib/auth.ts
databaseHooks: {
user: {
create: {
after: async (user) => {
const teamId = nanoid();
await db.insert(schema.teams).values({
id: teamId,
name: `${user.name}'s Organization`,
email: user.email,
slug: `${user.name.toLowerCase().replace(/\s+/g, "-")}-${Date.now().toString().slice(-4)}`,
});
await db.insert(schema.teamMembers).values({
teamId,
userId: user.id,
role: "owner",
});
},
},
},
},RBAC Roles
Two levels of roles exist independently:
App role (users.role) — platform-wide access:
| Role | Permissions |
|---|---|
superadmin | create, update, delete, ban, set-role, list users |
admin | create, update, ban, set-role, list users |
user | No admin permissions |
Team role (teamMembers.role) — per-team access:
| Role | Description |
|---|---|
owner | Created automatically on signup |
admin | Can manage team settings and members |
member | Standard team member |
Schema Mapping
The organization plugin is remapped to use your custom teams table:
organization({
schema: {
organization: schema.teams,
member: schema.teamMembers,
invitation: schema.invitations,
},
modelName: {
organization: "team",
},
})This means Better-Auth's organization features (invitations, member roles) all operate against your teams and teamMembers tables.
Auth Client
On the client side, import from lib/auth-client.ts:
import { authClient } from "@/lib/auth-client";
// Sign in
await authClient.signIn.email({ email, password });
// Sign in with Google
await authClient.signIn.social({ provider: "google" });
// Magic link
await authClient.signIn.magicLink({ email });Required Environment Variables
BETTER_AUTH_SECRET= # openssl rand -base64 32
GOOGLE_CLIENT_ID= # Google Cloud Console → APIs & Services → Credentials
GOOGLE_CLIENT_SECRET= # Google Cloud Console → APIs & Services → CredentialsAdd http://localhost:3000/api/auth/callback/google to your Google OAuth authorized redirect URIs for local development. See the Google Cloud Console to manage your OAuth client.
For more on the plugins used, see the Better-Auth organization plugin docs and the admin plugin docs.