Skip to content

Commit 8f2bb5f

Browse files
committed
docs: update authentication section
1 parent 591e09c commit 8f2bb5f

File tree

1 file changed

+174
-129
lines changed

1 file changed

+174
-129
lines changed

docs/security-next-auth.md

Lines changed: 174 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,242 @@
1+
## Autenticación con Better Auth
12

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.
34

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
66

7-
[Ver anuncio]: https://www.better-auth.com/blog/authjs-joins-better-auth
7+
Instalamos las dependencias necesarias en el proyecto:
88

9+
```bash
10+
npm install better-auth better-sqlite3
11+
npm install --save-dev @types/better-sqlite3
12+
```
913

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.
1115

12-
## Instalación
16+
## Variables de entorno
1317

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`).
1519

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
1826
```
1927

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.
2129

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`).
2531

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.
3535

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'
3740

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+
})
3951
```
4052

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.
4354

44-
```ts title="src/middleware.ts"
45-
export { default } from 'next-auth/middleware'
55+
## Crear las tablas
4656

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
4862
```
4963

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.
5365

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
5567

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:
5669

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'
5973

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+
```
6376

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.
7178

72-
import { config } from '@/app/api/auth/[...nextauth]/route'
79+
## Registrar la aplicación en GitHub
7380

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:
8382

84-
## Registro de nuestra aplicación en Github
83+
1. Accedemos a [https://github.com/settings/developers](https://github.com/settings/developers).
8584

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`
8988

90-
Necesitamos tres datos:
89+
![Registro de aplicación OAuth en Github](images/github-oauth-registry.png)
9190

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`.
9592

96-
![Registro de aplicación OAuth en Github](images/github-oauth-registry.png)
93+
![Credenciales de aplicación OAuth en Github](images/github-oauth-credentials.png)
9794

95+
4. Copiamos ambos valores en `.env.local`.
9896

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`
10297

10398

104-
![Credenciales de aplicación OAuth en Github](images/github-oauth-credentials.png)
10599

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*.
107102

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
114104

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`:
118106

119-
## Acceso a la web asegurara
107+
```ts title="src/lib/auth-client.ts"
108+
'use client'
120109

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'
123111

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+
})
126115

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
129122

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:
131124

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'
134129

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+
})
137134

138135
if (!session) {
139-
return <>Unauthorized</>
136+
redirect('/sign-in')
140137
}
141138

142-
const { user: { name } = {} } = session
143-
144-
return <>Hello, {name}.</>
139+
return <h1>Hola, {session.user.name ?? 'Usuario'}.</h1>
145140
}
146141
```
147142

148-
## Ampliar datos de la sesión
143+
## Sign in desde el cliente
149144

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`:
152146

153-
Modificamos nuestra configuración:
147+
```tsx title="src/components/login-button.tsx"
148+
'use client'
154149

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'
158152

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'
163154

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+
)
174168
}
169+
```
175170

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+
}
179201
```
180202

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
184206

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.
187208

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))
193218
}
219+
220+
return NextResponse.next()
221+
}
222+
223+
export const config = {
224+
matcher: ['/dashboard'],
194225
}
195226
```
196227

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

Comments
 (0)