|
| 1 | +## Autenticación con Better Auth |
1 | 2 |
|
2 | | -!!! danger |
| 3 | +En esta sección configuraremos Better Auth paso a paso utilizando GitHub como proveedor de autenticación y una base de datos SQLite local para almacenar las credenciales. |
3 | 4 |
|
4 | | - Next Auth ahora es parte de Better Auth [Ver anuncio]. Con lo que no se recomienda hacer nuevos proyectos con Next Auth. |
5 | | - Está sección será reescrita desde cero. Por lo que ahora mismo puede que no te funcione. |
| 5 | +## Instalación |
6 | 6 |
|
7 | | - [Ver anuncio]: https://www.better-auth.com/blog/authjs-joins-better-auth |
| 7 | +Instalamos las dependencias necesarias en el proyecto: |
8 | 8 |
|
| 9 | +```bash |
| 10 | +npm install better-auth better-sqlite3 |
| 11 | +npm install --save-dev @types/better-sqlite3 |
| 12 | +``` |
9 | 13 |
|
10 | | -NextAuth.js es una librería de autenticación que se integra fácilmente con aplicaciones web basadas en Next.js. Esta potente librería simplifica la implementación de la autenticación de usuarios, incluyendo la autenticación social y el manejo de sesiones. Con NextAuth.js, puedes configurar la autenticación de manera eficiente y segura, permitiendo a los usuarios acceder a tus aplicaciones de forma sencilla a través de múltiples proveedores, como Google, Facebook, GitHub y más. |
| 14 | +Vamos a usar SQLite con `better-sqlite3` porque es simple y no requiere instalar servidores adicionales. Si prefieres otro motor, puedes sustituirlo siguiendo la documentación oficial. |
11 | 15 |
|
12 | | -## Instalación |
| 16 | +## Variables de entorno |
13 | 17 |
|
14 | | -Lo primero es instalar el paquete, como siempre: |
| 18 | +Creamos (o actualizamos) el archivo `.env.local` con los valores que demanda Better Auth. La clave secreta se usa para encriptar y firmar cookies. Puedes generar una cadena aleatoria con cualquier herramienta (por ejemplo `openssl rand -base64 32`). |
15 | 19 |
|
16 | | -```bash |
17 | | -npm install next-auth |
| 20 | +```env title=".env.local" |
| 21 | +BETTER_AUTH_SECRET=pon_aqui_una_cadena_unica |
| 22 | +BETTER_AUTH_URL=http://localhost:3000 |
| 23 | +NEXT_PUBLIC_APP_URL=http://localhost:3000 |
| 24 | +GITHUB_CLIENT_ID=tu_client_id_de_github |
| 25 | +GITHUB_CLIENT_SECRET=tu_client_secret_de_github |
18 | 26 | ``` |
19 | 27 |
|
20 | | -Y a continuación creamos el fichero de configuración: |
| 28 | +`NEXT_PUBLIC_APP_URL` lo utilizamos en el cliente para construir la URL base cuando el proyecto se despliega en otro dominio. |
21 | 29 |
|
22 | | -```ts title="src/app/api/auth/[...nextauth]/route.ts" |
23 | | -import NextAuth, { NextAuthOptions } from 'next-auth' |
24 | | -import GithubProvider from 'next-auth/providers/github' |
| 30 | +El campo `BETTER_AUTH_URL` siempre debe apuntar a la URL pública de la aplicación (en local usaremos `http://localhost:3000`). |
25 | 31 |
|
26 | | -export const config: NextAuthOptions = { |
27 | | - providers: [ |
28 | | - GithubProvider({ |
29 | | - clientId: process.env.GITHUB_ID || '', |
30 | | - clientSecret: process.env.GITHUB_SECRET || '', |
31 | | - }), |
32 | | -], |
33 | | - secret: process.env.NEXTAUTH_SECRET, |
34 | | -} |
| 32 | +## Configurar Better Auth |
| 33 | + |
| 34 | +Creamos el archivo `src/lib/auth.ts` con la instancia de Better Auth. En este ejemplo habilitamos el plugin `nextCookies` para que las cookies se sincronicen automáticamente en server actions y server components, configuramos SQLite y registramos el proveedor de GitHub. |
35 | 35 |
|
36 | | -const handler = NextAuth(config) |
| 36 | +```ts title="src/lib/auth.ts" |
| 37 | +import { betterAuth } from 'better-auth' |
| 38 | +import { nextCookies } from 'better-auth/next-js' |
| 39 | +import Database from 'better-sqlite3' |
37 | 40 |
|
38 | | -export { handler as GET, handler as POST } |
| 41 | +export const auth = betterAuth({ |
| 42 | + database: new Database('./better-auth.sqlite'), |
| 43 | + plugins: [nextCookies()], |
| 44 | + socialProviders: { |
| 45 | + github: { |
| 46 | + clientId: process.env.GITHUB_CLIENT_ID as string, |
| 47 | + clientSecret: process.env.GITHUB_CLIENT_SECRET as string, |
| 48 | + }, |
| 49 | + }, |
| 50 | +}) |
39 | 51 | ``` |
40 | 52 |
|
41 | | -Y activamos el middleware que se encarga de garantizar que todas las llamadas a Next |
42 | | -son seguras: |
| 53 | +La base de datos SQLite (`better-auth.sqlite`) se crea automáticamente en la raíz del proyecto cuando ejecutemos las migraciones. |
43 | 54 |
|
44 | | -```ts title="src/middleware.ts" |
45 | | -export { default } from 'next-auth/middleware' |
| 55 | +## Crear las tablas |
46 | 56 |
|
47 | | -export const config = { matcher: ['/counter'] } |
| 57 | +Better Auth incluye una CLI que genera el esquema y las migraciones necesarias. Desde la raíz del proyecto ejecutamos: |
| 58 | + |
| 59 | +```bash |
| 60 | +npx @better-auth/cli generate |
| 61 | +npx @better-auth/cli migrate |
48 | 62 | ``` |
49 | 63 |
|
50 | | -!!! info |
51 | | - Por defecto el middleware asegura todas los accesos. Con `matcher` podemos restringir |
52 | | - qué rutas queremos proteger. Para este ejemplo solo vamos a asegurar nuestro contador. |
| 64 | +El primer comando crea la migración y el segundo la aplica sobre nuestro archivo SQLite. Si utilizas otro motor o un ORM, revisa la documentación oficial para adaptar estos pasos. |
53 | 65 |
|
54 | | - Mira la documentación de [middleware en next](https://nextjs.org/docs/app/building-your-application/routing/middleware) para saber como funciona `matcher`. |
| 66 | +## Ruta de API |
55 | 67 |
|
| 68 | +Next.js necesita exponer un endpoint que procese las peticiones de autenticación. Creamos el archivo `src/app/api/auth/[...all]/route.ts` con el manejador oficial: |
56 | 69 |
|
57 | | -En este fichero registraremos que proveedores de identidad queremos usar, en el |
58 | | -ejemplo usaremos GitHub, pero podemos usar muchísimos mas (Google, Spotify, Twitter,...). |
| 70 | +```ts title="src/app/api/auth/[...all]/route.ts" |
| 71 | +import { toNextJsHandler } from 'better-auth/next-js' |
| 72 | +import { auth } from '@/lib/auth' |
59 | 73 |
|
60 | | -Ahora necesitamos poder leer la sesión del usuario cuando esta logueado. NextAuth |
61 | | -nos da una función, pero como requiere pasarle un parámetro vamos a crear una propia |
62 | | -que nos ahorre trabajo cada vez que la queremos usar: |
| 74 | +export const { GET, POST } = toNextJsHandler(auth.handler) |
| 75 | +``` |
63 | 76 |
|
64 | | -```ts title="src/lib/next/auth.ts" |
65 | | -import type { |
66 | | - GetServerSidePropsContext, |
67 | | - NextApiRequest, |
68 | | - NextApiResponse, |
69 | | -} from 'next' |
70 | | -import { getServerSession } from 'next-auth' |
| 77 | +Recomendamos mantener la ruta `/api/auth/[...all]`, ya que coincidimos con lo que espera la librería y con la configuración de GitHub. |
71 | 78 |
|
72 | | -import { config } from '@/app/api/auth/[...nextauth]/route' |
| 79 | +## Registrar la aplicación en GitHub |
73 | 80 |
|
74 | | -export function auth( |
75 | | - ...args: |
76 | | - | [GetServerSidePropsContext['req'], GetServerSidePropsContext['res']] |
77 | | - | [NextApiRequest, NextApiResponse] |
78 | | - | [] |
79 | | -) { |
80 | | - return getServerSession(...args, config) |
81 | | -} |
82 | | -``` |
| 81 | +Para permitir el login social necesitamos crear una OAuth App en GitHub: |
83 | 82 |
|
84 | | -## Registro de nuestra aplicación en Github |
| 83 | +1. Accedemos a [https://github.com/settings/developers](https://github.com/settings/developers). |
85 | 84 |
|
86 | | -Para que podamos hacer login usando Github, necesitamos registrar una aplicación |
87 | | -OAuth. Para ello nos iremos a la web de registro de aplicaciones OAuth: |
88 | | -[https://github.com/settings/applications/new](https://github.com/settings/applications/new) |
| 85 | +2. Creamos una **OAuth App** con los siguientes valores en desarrollo: |
| 86 | + 1. **Homepage URL**: `http://localhost:3000` |
| 87 | + 1. **Authorization callback URL**: `http://localhost:3000/api/auth/callback/github` |
89 | 88 |
|
90 | | -Necesitamos tres datos: |
| 89 | +  |
91 | 90 |
|
92 | | -* **Application name**: El que queramos, por ejemplo: `Curso de NextJS del Aula de Software Libre` |
93 | | -* **Homepage URL**: Esta sería la url de nuestra web si estuviera publicada, por ahora usaremos: `http://localhost:3000/` |
94 | | -* **Authorization callback URL**: Esta es la url de NextAuth que finalizará el proceso de autenticación, debemos poner: `http://localhost:3000/api/auth/callback/github` |
| 91 | +3. Tras completar el formulario, GitHub nos mostrará el `Client ID`. Desde la misma pantalla generamos un nuevo `Client Secret`. |
95 | 92 |
|
96 | | - |
| 93 | +  |
97 | 94 |
|
| 95 | +4. Copiamos ambos valores en `.env.local`. |
98 | 96 |
|
99 | | -Una vez eso ya tendremos el primero de los dos datos que necesitamos el `GITHUB_ID`, es la cadena hexadecimal que |
100 | | -aparece en la web de la aplicación. A continuación debemos pulsar a `Generate a new client secret` para |
101 | | -conseguir el segundo dato necesario `GITHUB_SECRET` |
102 | 97 |
|
103 | 98 |
|
104 | | - |
105 | 99 |
|
106 | | -Y por último vamos a crear un archivo de credenciales donde guardar esos datos: |
| 100 | +!!! info |
| 101 | + Si recibes errores relacionados con el correo electrónico del usuario, revisa que tu GitHub App tenga el permiso *Email addresses* en modo lectura dentro de *Account permissions*. |
107 | 102 |
|
108 | | -```env title=".env.local" |
109 | | -GITHUB_ID=tu Client ID de la web de Github |
110 | | -GITHUB_SECRET=tu Client Secret de la web de Github |
111 | | -NEXTAUTH_URL=http://localhost:3000/api/auth |
112 | | -NEXTAUTH_SECRET=CHANGE_ME |
113 | | -``` |
| 103 | +## Cliente de Better Auth en React |
114 | 104 |
|
115 | | -!!! info |
116 | | - Recuerda que las credenciales son secretas, nunca las subas a Github. |
117 | | - El fichero `.env.local` está en el `.gitignore` así que no tienes que preocuparte. |
| 105 | +El cliente nos ayuda a iniciar sesión desde componentes de React y a recuperar la sesión desde el navegador. Creamos `src/lib/auth-client.ts`: |
118 | 106 |
|
119 | | -## Acceso a la web asegurara |
| 107 | +```ts title="src/lib/auth-client.ts" |
| 108 | +'use client' |
120 | 109 |
|
121 | | -Si intentamos acceder ahora a nuestro contador, veremos que nos saltará la página |
122 | | -para loguearnos en Github. Solo si nos identificamos podremos verla. |
| 110 | +import { createAuthClient } from 'better-auth/react' |
123 | 111 |
|
124 | | -Para conseguir ver los datos del usuario es tan facil como hacer lo siguiente en cualquier |
125 | | -lugar donde lo necesitemos: |
| 112 | +export const authClient = createAuthClient({ |
| 113 | + baseURL: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000', |
| 114 | +}) |
126 | 115 |
|
127 | | -```ts |
128 | | -import { Session } from 'next-auth' |
| 116 | +export const { signIn, signOut, useSession } = authClient |
| 117 | +``` |
| 118 | + |
| 119 | +Exportamos los métodos que utilizaremos en los componentes (`signIn`, `signOut`, `useSession`). Puedes añadir más helpers si los necesitas. |
| 120 | + |
| 121 | +## Proteger páginas y obtener la sesión |
129 | 122 |
|
130 | | -import { auth } from '@/lib/next/auth' |
| 123 | +Better Auth expone un API preparado para ejecutarse en componentes de servidor o server actions. En este ejemplo recuperamos la sesión dentro de una página del directorio `app` y redirigimos a los usuarios no autenticados: |
131 | 124 |
|
132 | | -export default async function Page() { |
133 | | - const session: Session | null = await auth() |
| 125 | +```tsx title="src/app/dashboard/page.tsx" |
| 126 | +import { headers } from 'next/headers' |
| 127 | +import { redirect } from 'next/navigation' |
| 128 | +import { auth } from '@/lib/auth' |
134 | 129 |
|
135 | | - // Podemos ver en la consola de NextJS todos los datos del servidor |
136 | | - console.table(session) |
| 130 | +export default async function DashboardPage() { |
| 131 | + const session = await auth.api.getSession({ |
| 132 | + headers: await headers(), |
| 133 | + }) |
137 | 134 |
|
138 | 135 | if (!session) { |
139 | | - return <>Unauthorized</> |
| 136 | + redirect('/sign-in') |
140 | 137 | } |
141 | 138 |
|
142 | | - const { user: { name } = {} } = session |
143 | | - |
144 | | - return <>Hello, {name}.</> |
| 139 | + return <h1>Hola, {session.user.name ?? 'Usuario'}.</h1> |
145 | 140 | } |
146 | 141 | ``` |
147 | 142 |
|
148 | | -## Ampliar datos de la sesión |
| 143 | +## Sign in desde el cliente |
149 | 144 |
|
150 | | -En ocasiones necesitamos saber más datos provenientes del proveedor de identidad. Uno |
151 | | -de ellos es el `id`, que no viene. Vamos a añadirlo. |
| 145 | +En el lado del cliente podemos llamar a `signIn.social` para abrir la ventana de GitHub. Crea, por ejemplo, un componente `LoginButton`: |
152 | 146 |
|
153 | | -Modificamos nuestra configuración: |
| 147 | +```tsx title="src/components/login-button.tsx" |
| 148 | +'use client' |
154 | 149 |
|
155 | | -```ts title="src/app/api/auth/[...nextauth]/route.ts" hl_lines="5-11" |
156 | | -import NextAuth, { NextAuthOptions } from 'next-auth' |
157 | | -import GithubProvider from 'next-auth/providers/github' |
| 150 | +import { CommandLineIcon } from '@heroicons/react/24/solid' |
| 151 | +import { Button } from '@heroui/react' |
158 | 152 |
|
159 | | -export const config: NextAuthOptions = { |
160 | | - callbacks: { |
161 | | - async session({ session, token }) { |
162 | | - session.user.id = token.sub |
| 153 | +import { signIn } from '@/lib/auth-client' |
163 | 154 |
|
164 | | - return session |
165 | | - }, |
166 | | - }, |
167 | | - providers: [ |
168 | | - GithubProvider({ |
169 | | - clientId: process.env.GITHUB_ID || '', |
170 | | - clientSecret: process.env.GITHUB_SECRET || '', |
171 | | - }), |
172 | | - ], |
173 | | - secret: process.env.NEXTAUTH_SECRET, |
| 155 | +export function LoginButton() { |
| 156 | + return ( |
| 157 | + <Button |
| 158 | + color="primary" |
| 159 | + onPress={() => |
| 160 | + signIn.social({ provider: 'github', callbackURL: '/dashboard' }) |
| 161 | + } |
| 162 | + startContent={<CommandLineIcon className="h-5 w-5" />} |
| 163 | + variant="solid" |
| 164 | + > |
| 165 | + Entrar con GitHub |
| 166 | + </Button> |
| 167 | + ) |
174 | 168 | } |
| 169 | +``` |
175 | 170 |
|
176 | | -const handler = NextAuth(config) |
177 | | - |
178 | | -export { handler as GET, handler as POST } |
| 171 | +El cliente gestiona la apertura del flujo OAuth y, tras regresar de GitHub, refresca la sesión automáticamente. |
| 172 | + |
| 173 | +## Página de inicio de sesión |
| 174 | + |
| 175 | +Cuando redirigimos a `/sign-in` necesitamos una vista sencilla que invite al usuario a iniciar sesión con GitHub. Usaremos componentes de HeroUI y un icono de Heroicons para mantener la estética del curso. Crea el archivo `src/app/sign-in/page.tsx` con el siguiente contenido: |
| 176 | + |
| 177 | +```tsx title="src/app/sign-in/page.tsx" |
| 178 | +import { LockClosedIcon } from '@heroicons/react/24/solid' |
| 179 | +import { Card, CardBody, CardHeader } from '@heroui/react' |
| 180 | + |
| 181 | +import { LoginButton } from '@/components/login-button' |
| 182 | + |
| 183 | +export default function SignInPage() { |
| 184 | + return ( |
| 185 | + <div className="flex min-h-screen items-center justify-center bg-default-100"> |
| 186 | + <Card className="w-[320px]"> |
| 187 | + <CardHeader className="flex flex-col items-center gap-2 text-center"> |
| 188 | + <LockClosedIcon className="h-8 w-8 text-primary" /> |
| 189 | + <h1 className="font-semibold text-large">Necesitas iniciar sesión</h1> |
| 190 | + </CardHeader> |
| 191 | + <CardBody className="flex flex-col gap-4 text-center"> |
| 192 | + <p className="text-default-500 text-sm"> |
| 193 | + Usa tu cuenta de GitHub para continuar. |
| 194 | + </p> |
| 195 | + <LoginButton /> |
| 196 | + </CardBody> |
| 197 | + </Card> |
| 198 | + </div> |
| 199 | + ) |
| 200 | +} |
179 | 201 | ``` |
180 | 202 |
|
181 | | -Estamos añadiendo un campo extra, desgraciadamente ese campo no existe en el interfaz |
182 | | -de usuario original, pero podemos ampliarlo. Esta técnica se llama interface augmention. |
183 | | -Creamos el siguiente fichero: |
| 203 | +Esta página funciona como destino tanto para los usuarios que llegan de forma directa como para los que han sido redirigidos por el middleware. Al reutilizar `LoginButton` mantenemos la lógica en un único sitio. |
| 204 | + |
| 205 | +## Middleware opcional |
184 | 206 |
|
185 | | -```ts title="src/types/next-auth.d.ts" |
186 | | -import { DefaultSession } from 'next-auth' |
| 207 | +Si necesitas redirigir rápidamente desde el `middleware` de Next.js, puedes comprobar la existencia de la cookie de sesión utilizando el helper `getSessionCookie`. Ten en cuenta que esta verificación es optimista: siempre valida la sesión en el servidor antes de mostrar datos sensibles. |
187 | 208 |
|
188 | | -declare module 'next-auth' { |
189 | | - interface Session { |
190 | | - user: { |
191 | | - id?: string | null |
192 | | - } & DefaultSession['user'] |
| 209 | +```ts title="src/middleware.ts" |
| 210 | +import { NextRequest, NextResponse } from 'next/server' |
| 211 | +import { getSessionCookie } from 'better-auth/cookies' |
| 212 | + |
| 213 | +export function middleware(request: NextRequest) { |
| 214 | + const sessionCookie = getSessionCookie(request) |
| 215 | + |
| 216 | + if (!sessionCookie) { |
| 217 | + return NextResponse.redirect(new URL('/sign-in', request.url)) |
193 | 218 | } |
| 219 | + |
| 220 | + return NextResponse.next() |
| 221 | +} |
| 222 | + |
| 223 | +export const config = { |
| 224 | + matcher: ['/dashboard'], |
194 | 225 | } |
195 | 226 | ``` |
196 | 227 |
|
197 | | -Y ya el interprete de Typescript debe dejar de dar error porque el campo id no existe. |
| 228 | +### Validación completa en cada página |
| 229 | + |
| 230 | +La forma segura de proteger rutas es verificar la sesión dentro de la propia página o en la acción del servidor que vaya a ejecutar lógica sensible (como hicimos en el ejemplo anterior con `auth.api.getSession`). |
| 231 | + |
| 232 | +## Resumen |
| 233 | + |
| 234 | +1. Instalamos `better-auth` y configuramos una base de datos (en este curso usamos SQLite). |
| 235 | +2. Creamos un archivo `auth.ts` con la instancia, habilitamos GitHub como proveedor social y registramos el plugin `nextCookies`. |
| 236 | +3. Configuramos la ruta `/api/auth/[...all]` con `toNextJsHandler`. |
| 237 | +4. Registramos la aplicación en GitHub y añadimos las credenciales en `.env.local`. |
| 238 | +5. Creamos un cliente (`auth-client.ts`) para iniciar sesión desde componentes. |
| 239 | +6. Añadimos la página `/sign-in` reutilizando `LoginButton` para ofrecer el acceso social. |
| 240 | +7. Protegemos páginas recuperando la sesión con `auth.api.getSession`. |
| 241 | + |
| 242 | +Con estos pasos ya tenemos autenticación social con GitHub funcionando en nuestro proyecto Next.js usando Better Auth. |
0 commit comments