Ir al contenido
angel.library

Better Auth

Better Auth es una librería de autenticación y autorización para TypeScript, agnóstica del framework y sin servicio externo: las tablas de usuarios y sesiones viven en tu base de datos y el proveedor de identidad sigue siendo tu propia aplicación. Licencia MIT.

La diferencia con armar el auth a mano es lo que ya trae resuelto: hash de contraseñas, emisión y rotación de sesiones en cookie, verificación por correo, OAuth con los proveedores habituales y un sistema de plugins para lo que normalmente se posterga (2FA, passkeys, organizaciones, rate limit).

Esta entrada cubre el paquete en general. Para la integración concreta hay guías por framework: Astro, Express y Next.js.

Ventana de terminal
pnpm add better-auth

betterAuth() devuelve un objeto con un handler (un Request → Response estándar) y una API para usar desde el servidor.

src/lib/auth.ts
import { betterAuth } from "better-auth"
import { prismaAdapter } from "better-auth/adapters/prisma"
import { prisma } from "./prisma"
export const auth = betterAuth({
database: prismaAdapter(prisma, { provider: "postgresql" }),
emailAndPassword: { enabled: true },
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!
}
}
})

Dos variables de entorno son obligatorias y solo del servidor:

.env
BETTER_AUTH_SECRET="una-cadena-larga-y-aleatoria"
BETTER_AUTH_URL="http://localhost:4321"

BETTER_AUTH_SECRET firma las sesiones: si cambia, todas caducan. BETTER_AUTH_URL es la base que se usa para armar los callbacks de OAuth — en producción debe ser la URL pública real.

Better Auth necesita sus tablas (user, session, account, verification, más las que agreguen los plugins). Su CLI las deriva de tu configuración:

Ventana de terminal
pnpm dlx @better-auth/cli generate

Con un ORM que versiona migraciones (Prisma, Drizzle) esto genera el esquema y la migración la crea y aplica el ORM. Con conexión directa a la base, migrate puede aplicarla él mismo:

Ventana de terminal
pnpm dlx @better-auth/cli migrate

Revisa el diff antes de aplicar nada en producción: agregar un plugin cambia el esquema.

Todo pasa por una ruta catch-all bajo /api/auth. En cualquier framework con Request/Response estándar es una línea:

// El handler recibe la Request y devuelve la Response.
export const ALL = ({ request }: { request: Request }) =>
auth.handler(request)
src/lib/auth-client.ts
import { createAuthClient } from "better-auth/client"
export const authClient = createAuthClient({
baseURL: "http://localhost:4321"
})
await authClient.signUp.email({
email: "ana@ejemplo.com",
password: "una-contraseña-larga",
name: "Ana"
})
await authClient.signIn.email({
email: "ana@ejemplo.com",
password: "una-contraseña-larga"
})
await authClient.signIn.social({ provider: "github" })
await authClient.signOut()

Hay entradas específicas por framework (better-auth/react, better-auth/vue, better-auth/svelte) que añaden un hook reactivo de sesión, como useSession.

La comprobación que importa es esta, y va en el servidor — nunca solo en el cliente:

const session = await auth.api.getSession({
headers: request.headers
})
if (!session) {
return new Response("No autorizado", { status: 401 })
}
session.user.id

Se activan en la configuración y casi siempre agregan tablas y métodos al cliente:

PluginQué añade
twoFactorSegundo factor por TOTP y códigos de respaldo
passkeyInicio de sesión con WebAuthn
organizationOrganizaciones, miembros, invitaciones y roles
adminGestión de usuarios: banear, listar, impersonar
magicLinkAcceso por enlace enviado al correo
jwtEmitir JWT para consumidores que no usan cookies
import { twoFactor } from "better-auth/plugins"
export const auth = betterAuth({
// ...
plugins: [twoFactor()]
})

Cada plugin nuevo suele implicar volver a correr generate y migrar.

  • El secret y las credenciales de OAuth son del servidor: si aparecen en un bundle de cliente, están comprometidas.
  • En producción, cookies con Secure y dominio correcto; con subdominios hay que configurarlo explícitamente.
  • La verificación de correo y el rate limit no están activos por defecto — decídelos antes de abrir el registro.
  • El proyecto se mueve rápido: fija la versión en package.json y revisa el changelog antes de subir de minor.