Inicio de sesión OAuth en Next.js: guía práctica para Google, GitHub y WeChat

La semana pasada, en un proyecto comunitario, el product manager me dijo: «Añade inicio de sesión con Google, debería ser rápido». Pensé que el login de terceros no sería complicado: ya había leído varios tutoriales. Resultado: toda una tarde, errores de redirect_uri, fallos al obtener tokens y la consola llena de mensajes rojos. Lo peor: seguí la documentación oficial paso a paso y no funcionaba.
El problema no era el código, sino una comprensión demasiado superficial de OAuth. Código de autorización, access token, callback: cada término por sí solo tiene sentido; juntos, confusión total.
Esta vez explico OAuth con palabras sencillas — no el RFC abstracto, sino la analogía de recoger un paquete. Entenderás por qué hacen falta code y token, para qué sirve el callback y dónde suelen fallar las configuraciones. Luego configuramos tres logins: Google (estándar internacional), GitHub (amigable para desarrolladores) y WeChat (indispensable en China, el más difícil).
WeChat es la parte más molesta: documentación poco clara, requisitos empresariales, depuración local complicada. Pero en proyectos orientados al mercado chino no se puede evitar; comparto las trampas que he encontrado, incluidos túneles y proveedores personalizados. Con una sola arquitectura puedes cubrir login local e internacional.
Cómo funciona realmente OAuth
Entender OAuth con la analogía de recoger un paquete
Cuando encontré OAuth por primera vez, authorization code y access token me volvieron loco. Luego lo entendí: es exactamente como pedirle a un amigo que recoja un paquete.
Imagina que tienes un paquete en el punto de recogida (información del usuario), pero estás en la oficina y no puedes ir. Tu amigo (tu app Next.js) se ofrece a recogerlo. El punto no entrega paquetes a cualquiera: debe confirmar que tú lo autorizaste. El flujo es así:
1. Le das a tu amigo un código de recogida — es el authorization code. Al pulsar «Iniciar sesión con Google», saltas a la página de autorización de Google; tras aceptar, Google genera un code temporal y lo devuelve a tu app por la URL.
2. Tu amigo va al punto con el código — tu backend usa el code para obtener un access token. El punto también verifica que tu amigo es de confianza: por eso registraste antes su «documento» (client_secret).
3. Tras verificar, entregan el paquete — si code y secret coinciden, entregan el paquete (datos del usuario) a tu amigo, que te lo pasa. Eso es usar access_token para obtener el perfil.
Lo clave: el code es de un solo uso; aunque alguien lo vea, sin el secret el punto no entrega nada. Por eso el code puede ir en la URL del navegador, pero el secret debe quedarse en el backend.
Los cuatro pasos clave
En términos técnicos:
Paso 1: redirigir a la página de autorización OAuth
Al pulsar «Iniciar sesión con Google», el frontend construye una URL y redirige a Google:
https://accounts.google.com/o/oauth2/v2/auth?
client_id=tu-app-id
&redirect_uri=http://localhost:3000/api/auth/callback/google
&response_type=code
&scope=email profile
Parámetros importantes:
client_id: identificador de tu app en Googleredirect_uri: adónde Google devuelve al usuario tras autorizarscope: qué datos del usuario quieres
Paso 2: el usuario autoriza y recibe el code
Tras pulsar «Permitir», Google redirige a tu app con una URL como:
http://localhost:3000/api/auth/callback/google?code=4/0AfJohXl...&state=random123
Ese code es el código de recogida: vale poco (5-10 minutos) y solo se usa una vez.
Paso 3: el backend intercambia code por access_token
El backend (no el frontend) envía code + client_secret a Google:
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
body: JSON.stringify({
code: 'el code recibido',
client_id: 'your_client_id',
client_secret: 'your_secret', // nunca al frontend
redirect_uri: 'http://localhost:3000/api/auth/callback/google',
grant_type: 'authorization_code'
})
})
const { access_token } = await response.json()
El access_token es la llave real para obtener datos del usuario.
Paso 4: obtener el perfil con access_token
Con el token llamas a la API de Google:
const userInfo = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: {
Authorization: `Bearer ${access_token}`
}
})
const user = await userInfo.json()
// { email: "[email protected]", name: "Juan Pérez", picture: "URL del avatar" }
Con esos datos creas o actualizas el usuario en tu base de datos, generas la sesión y completas el login.
Tabla rápida de conceptos
| Término | Explicación sencilla | Dónde verlo |
|---|---|---|
| Client ID | Identificador público de tu app | .env.local, parámetros de URL |
| Client Secret | Contraseña de la app, confidencial | Solo backend y variables de entorno |
| Authorization Code | Código de recogida de un solo uso | Parámetro code en la URL de callback |
| Access Token | Llave real para obtener datos | Backend, no al frontend |
| Redirect URI | URL de retorno tras autorizar | Config OAuth, parámetro de autorización |
| Scope | Permisos solicitados | Parámetro scope, p. ej. email profile |
| State | Cadena aleatoria anti-CSRF | Parámetros de autorización y callback |
Para qué sirve State: generas una cadena aleatoria (p. ej. abc123), la guardas en sesión y la envías al servidor OAuth. En el callback compruebas que el state devuelto coincide. Si no, puede ser un ataque — rechaza. NextAuth.js lo gestiona automáticamente.
¿Por qué no devolver el token directamente?
Si al final necesitas access_token, ¿por qué no devolverlo en la URL y evitar el paso code → token?
Por seguridad.
La barra de direcciones es visible en historial, logs y extensiones. Devolver el token ahí sería exponer la llave de los datos del usuario.
Aunque intercepten el code:
- Solo sirve una vez
- Para cambiarlo hace falta client_secret, solo en el backend
- El servidor OAuth valida también redirect_uri
Aunque roben el code, sin secret no hay token. Este diseño se llama Authorization Code Flow: el flujo más seguro de OAuth 2.0 para apps web con backend. El Implicit Flow (token directo en URL) ya no se recomienda.
Primeros pasos con NextAuth.js
Por qué NextAuth.js
En Next.js puedes implementar OAuth a mano o con una librería. Lo intenté yo mismo, tropecé mil veces y pasé a NextAuth.js.
Motivo 1: respaldo oficial y ecosistema maduro
NextAuth.js es la opción recomendada en la documentación de Next.js, con 70K+ estrellas y mantenimiento activo. La v5 (noviembre 2024) soporta App Router y Server Components.
Motivo 2: más de 30 proveedores OAuth
Google, GitHub, Facebook, Twitter, etc. salen con pocas líneas. Para WeChat, sin proveedor integrado, hay mecanismo de Provider personalizado.
Motivo 3: lógica compleja automatizada
Sesiones, firma JWT, almacenamiento, protección CSRF — automático. Tú te centras en el negocio, p. ej. enviar email de bienvenida en el primer login.
Estructura del archivo de configuración
El núcleo es una API route. Con App Router (Next.js 13+):
app/api/auth/[...nextauth]/route.ts
[...nextauth] es catch-all: atiende /api/auth/*:
/api/auth/signin— página de login/api/auth/callback/google— callback de Google/api/auth/signout— cerrar sesión/api/auth/session— sesión actual
Configuración básica:
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import GitHubProvider from "next-auth/providers/github"
export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
],
pages: {
signIn: '/login',
},
callbacks: {
async signIn({ user, account, profile }) {
// comprobar lista blanca, etc.
return true // false bloquea el login
},
async session({ session, token }) {
session.user.id = token.sub
return session
},
},
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
La línea export { handler as GET, handler as POST } es obligatoria en App Router.
Convenciones de variables de entorno
# .env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key-here
# o en v5:
AUTH_SECRET=your-secret-key-here
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# o AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET
GITHUB_ID=your-github-client-id
GITHUB_SECRET=your-github-client-secret
# o AUTH_GITHUB_ID / AUTH_GITHUB_SECRET
Puntos clave:
-
NEXTAUTH_URL: URL completa de la app. Local:
http://localhost:3000. Producción: dominio real con https. -
NEXTAUTH_SECRET / AUTH_SECRET: clave para firmar JWT. Genera con:
openssl rand -base64 32
No la subas a Git. Si se filtra, cámbiala de inmediato.
- Prefijo AUTH_: en v5,
AUTH_PROVIDER_IDyAUTH_PROVIDER_SECRETse reconocen solos.
Instalación y configuración mínima
Paso 1: instalar
npm install next-auth
# o
pnpm add next-auth
Paso 2: crear la API route
Crea app/api/auth/[...nextauth]/route.ts con el código anterior.
Paso 3: crear .env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=cadena generada con openssl rand -base64 32
Paso 4: botón de login en el frontend
// app/login/page.tsx
'use client'
import { signIn } from 'next-auth/react'
export default function LoginPage() {
return (
<div>
<button onClick={() => signIn('google')}>
Iniciar sesión con Google
</button>
<button onClick={() => signIn('github')}>
Iniciar sesión con GitHub
</button>
</div>
)
}
Paso 5: envolver la app con Provider
// app/layout.tsx
import { SessionProvider } from 'next-auth/react'
export default function RootLayout({ children }) {
return (
<html>
<body>
<SessionProvider>
{children}
</SessionProvider>
</body>
</html>
)
}
Con esto tienes el esqueleto. El botón fallará hasta configurar las apps OAuth de Google y GitHub.
Configuración de login con Google
Google Cloud Console
Paso 1: entrar en Google Cloud Console
Visita https://console.cloud.google.com. Si es la primera vez, crea un proyecto, p. ej. «Mi blog».
Paso 2: habilitar Google+ API
Menú «APIs & Services» → «Library», busca «Google+ API» y pulsa «Enable».
Paso 3: crear credenciales OAuth
- «Credentials» → «Create Credentials» → «OAuth client ID»
- Si es la primera vez, configura «OAuth consent screen» (nombre, email de soporte)
- Application type: «Web application»
Paso 4: Redirect URIs (crítico)
En Authorized redirect URIs:
Desarrollo:
http://localhost:3000/api/auth/callback/google
Producción (tras desplegar):
https://yourdomain.com/api/auth/callback/google
Detalles:
- Local: http; producción: https obligatorio
- Si usas puerto 3001, inclúyelo
- La ruta
/api/auth/callback/googledebe ser exacta - No añadas query params; Google añade
?code=xxx
Copia Client ID y Client Secret.
Paso 5: .env.local
GOOGLE_CLIENT_ID=tu-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxx
Integración en Next.js
// app/api/auth/[...nextauth]/route.ts
import GoogleProvider from "next-auth/providers/google"
export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code"
}
}
}),
],
}
prompt: "consent": muestra la pantalla de consentimiento cada vez (útil en pruebas)access_type: "offline": refresh token si necesitas acceso prolongadoresponse_type: "code": Authorization Code Flow explícito
Probar el flujo
npm run dev
Visita http://localhost:3000/login, inicia sesión con Google.
Obtener sesión en servidor:
// app/page.tsx
import { getServerSession } from "next-auth"
import { authOptions } from "./api/auth/[...nextauth]/route"
export default async function Home() {
const session = await getServerSession(authOptions)
if (session) {
return <div>Hola, {session.user?.name}</div>
}
return <div>No has iniciado sesión</div>
}
En cliente:
'use client'
import { useSession } from "next-auth/react"
export default function Profile() {
const { data: session, status } = useSession()
if (status === "loading") return <div>Cargando...</div>
if (!session) return <div>No has iniciado sesión</div>
return (
<div>
<img src={session.user?.image} alt="Avatar" />
<p>{session.user?.name}</p>
<p>{session.user?.email}</p>
</div>
)
}
Errores frecuentes
Error 1: redirect_uri_mismatch
Error 400: redirect_uri_mismatch
The redirect URI in the request, http://localhost:3000/api/auth/callback/google,
does not match the ones authorized for the OAuth client.
La URI en Google Console no coincide con la real.
Pasos:
- DevTools → Network
- Al pulsar login, copia el parámetro
redirect_uri - Pégalo tal cual en Authorized redirect URIs
- Revisa protocolo, puerto, barras
Error 2: Access blocked: This app’s request is invalid
Falta OAuth consent screen o usuarios de prueba.
- Google Console → OAuth consent screen
- User Type: External o Internal
- Si External sin publicar, añade emails en Test users
Error 3: puerto distinto
Local en 3001 pero URI en 3000 → error. Usa siempre 3000 o añade varios URIs.
Error 4: HTTPS en producción
Google exige https en producción. Vercel y Netlify lo dan por defecto.
Configuración de login con GitHub
GitHub OAuth App
Paso 1: Developer settings
GitHub → avatar → Settings → Developer settings → OAuth Apps → New OAuth App.
Paso 2: datos de la app
- Application name: cualquiera
- Homepage URL:
http://localhost:3000(local) o tu dominio - Authorization callback URL:
http://localhost:3000/api/auth/callback/github
Register application → Client ID. Generate client secret (solo se muestra una vez).
Paso 3: .env.local
GITHUB_ID=tu-client-id
GITHUB_SECRET=tu-client-secret
Integración Next.js
import GitHubProvider from "next-auth/providers/github"
export const authOptions = {
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
],
}
GitHub vs Google
| Aspecto | GitHub | |
|---|---|---|
| Dificultad | Media, hay que habilitar API | Simple |
| Redirect URI | Coincidencia estricta | Más flexible |
| HTTPS | Obligatorio en producción | localhost puede ser http |
| Scope | Hay que definir | Por defecto suele bastar |
| Datos | email, name, picture | login, email, avatar_url |
El email de GitHub puede ser null si el usuario oculta el correo:
const userEmail = session.user?.email || 'Correo no proporcionado'
Login con WeChat (escenario China)
Tres formas de login WeChat
| Tipo | Escenario | Requisitos | Experiencia |
|---|---|---|---|
| Open Platform — web | Sitio independiente | Empresa, dominio registrado, HTTPS | QR en PC |
| Autorización web de cuenta oficial | H5 dentro de WeChat | Cuenta oficial verificada | Solo navegador WeChat |
| WeChat Work | Sistemas internos | Cuenta WeChat Work | Solo empleados |
Aquí cubrimos Open Platform — web app, adecuada para un sitio Next.js independiente.
WeChat Open Platform (barrera alta)
Requisitos:
- Licencia de empresa (no vale desarrollador individual)
- Dominio registrado (ICP)
- Certificado HTTPS
Paso 1: registrar cuenta de desarrollador
https://open.weixin.qq.com → Register → Website App Developer → subir licencia (1-2 días hábiles).
Paso 2: crear web app
Centro de gestión → Web Apps → Create:
- Nombre, descripción
- Sitio oficial: tu dominio registrado
- Dominio de callback: solo dominio, sin protocolo ni ruta, p. ej.
yourdomain.com
A diferencia de Google/GitHub, WeChat pide dominio, no URL completa.
Tras aprobación (1-7 días): AppID y AppSecret.
Paso 3: variables
WECHAT_APP_ID=tu-app-id
WECHAT_APP_SECRET=tu-app-secret
Provider personalizado en NextAuth.js
// app/api/auth/[...nextauth]/route.ts
const WeChatProvider = {
id: "wechat",
name: "WeChat",
type: "oauth",
authorization: {
url: "https://open.weixin.qq.com/connect/qrconnect",
params: {
appid: process.env.WECHAT_APP_ID,
scope: "snsapi_login",
response_type: "code",
},
},
token: {
url: "https://api.weixin.qq.com/sns/oauth2/access_token",
async request({ params, provider }) {
const response = await fetch(
`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${process.env.WECHAT_APP_ID}&secret=${process.env.WECHAT_APP_SECRET}&code=${params.code}&grant_type=authorization_code`
)
const tokens = await response.json()
return { tokens }
},
},
userinfo: {
url: "https://api.weixin.qq.com/sns/userinfo",
async request({ tokens }) {
const response = await fetch(
`https://api.weixin.qq.com/sns/userinfo?access_token=${tokens.access_token}&openid=${tokens.openid}`
)
return await response.json()
},
},
profile(profile) {
return {
id: profile.unionid || profile.openid,
name: profile.nickname,
email: null,
image: profile.headimgurl,
}
},
}
export const authOptions = {
providers: [
WeChatProvider,
],
}
Depuración en local
WeChat exige HTTPS y dominio registrado. Opciones:
Opción 1: túnel (recomendado)
ngrok http 3000
# o en China:
cpolar http 3000
Dominio temporal, p. ej. https://abc123.ngrok.io → configúralo como dominio de callback en WeChat.
Opción 2: cuenta de prueba
- https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
- AppID/Secret de prueba tras escaneo QR
- Callback vía dominio del túnel
Solo tú puedes probar; otros verán «cuenta no seguida».
Particularidades de WeChat
openid y unionid
openid: identificador en la app actualunionid: mismo usuario en apps bajo la misma cuenta Open Platform
Prefiere unionid; si no hay, openid.
Sin correo: email es null; pide email aparte si lo necesitas.
Token corto: ~2 h y refrescos limitados (~10/día). Gestiona la renovación.
Resumen
Desde OAuth hasta Google, GitHub y WeChat: flujo completo de login de terceros.
Puntos clave:
- Code + token en dos pasos = seguridad; el code puede ir en el navegador, el secret solo en backend
- NextAuth.js gestiona sesión y CSRF; tú el negocio
- Google es estricto con redirect URI; GitHub el más sencillo; WeChat el más exigente pero indispensable en China
- WeChat: empresa + dominio registrado; túnel + cuenta de prueba en desarrollo
Consejos:
openssl rand -base64 32para NEXTAUTH_SECRET- Varios puertos en Google Console
- Email de GitHub nullable: compruébalo
- unionid > openid entre apps
Lo más difícil no es el código, sino el modelo mental de OAuth y las diferencias por plataforma. Si esto te evita algunos obstáculos, misión cumplida.
La próxima vez que te pidan «añade login con Google», no debería costarte toda una tarde.
Flujo completo de configuración OAuth en Next.js
Desde entender OAuth hasta configurar Google, GitHub y WeChat
⏱️ Estimated time: 2 hr
- 1
Step 1: Entender OAuth (analogía del paquete)
Idea central de OAuth: no entregas tu contraseña a una app de terceros; autorizas un pase temporal con el proveedor.
Analogía del paquete:
• Tú (usuario) → quien quiere acceder
• Amigo (app Next.js) → quien recoge el paquete
• Punto de recogida (proveedor OAuth) → Google, GitHub, WeChat
• Tarjeta permanente (contraseña) → no se comparte
• Pase temporal (access_token) → limitado en tiempo y permisos
Cuatro pasos:
1. Das el código de recogida (authorization code)
2. Tu amigo va al punto con código + documento (code + client_secret → access_token)
3. El punto verifica y entrega el paquete (información del usuario)
4. Tu amigo te lo pasa (login exitoso)
Puntos clave:
• El code es de un solo uso, ~10 minutos
• client_secret solo en el servidor
• access_token limitado en duración y alcance - 2
Step 2: Configurar Google
1. Crear cliente OAuth en Google Cloud Console:
• https://console.cloud.google.com
• Proyecto → APIs y servicios → Credenciales → ID de cliente OAuth
• Tipo: aplicación web
• URI de redirección: http://localhost:3000/api/auth/callback/google
2. Obtener client_id y client_secret
3. Configurar NextAuth.js:
```ts
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
})
],
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
```
4. Variables de entorno:
```
GOOGLE_CLIENT_ID=tu_client_id
GOOGLE_CLIENT_SECRET=tu_client_secret
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=cadena_aleatoria
```
5. En la página:
```tsx
import { signIn } from 'next-auth/react'
<button onClick={() => signIn('google')}>
Iniciar sesión con Google
</button>
``` - 3
Step 3: Configurar GitHub
1. Crear OAuth App en GitHub:
• https://github.com/settings/developers
• New OAuth App
• Authorization callback URL: http://localhost:3000/api/auth/callback/github
2. Obtener Client ID y Client Secret
3. NextAuth.js:
```ts
import GitHubProvider from 'next-auth/providers/github'
providers: [
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
})
]
```
4. Variables:
```
GITHUB_CLIENT_ID=tu_client_id
GITHUB_CLIENT_SECRET=tu_client_secret
```
Misma lógica que Google, proveedor distinto. - 4
Step 4: Configurar WeChat (caso especial)
1. Registrar app en WeChat Open Platform:
• https://open.weixin.qq.com
• Crear aplicación web
• AppID y AppSecret
• Requiere calificación empresarial
2. Dominio de callback:
• Formato: tudominio.com (sin http:// ni https://)
• Dominio con registro ICP
3. Provider personalizado:
```ts
import WeChatProvider from 'next-auth/providers/wechat'
providers: [
WeChatProvider({
clientId: process.env.WECHAT_CLIENT_ID!,
clientSecret: process.env.WECHAT_CLIENT_SECRET!,
})
]
```
4. Depuración local con túnel:
• ngrok o frp
• Callback = dirección del túnel
• En producción: dirección real
Puntos clave:
• Calificación empresarial
• Túnel en local
• unionid preferible a openid - 5
Step 5: Resolver errores comunes
Error 1: redirect_uri_mismatch
• Causa: URI de callback incorrecta
• Solución: configurar redirect_uri en el proveedor
• Declara local y producción
Error 2: faltan variables de entorno
• Comprueba .env.local
• Nombres correctos
• También en Vercel Dashboard
Error 3: OK en local, falla en producción
• URI de callback distinta
• Añade https://yourdomain.com/api/auth/callback/google
Seguridad:
• client_secret solo en servidor
• Parámetro state anti-CSRF
• Verificar state en el callback
FAQ
¿Cómo funciona realmente OAuth?
Escenario: tienes un paquete (tus datos) en el punto de recogida, estás en la oficina. Un amigo (app Next.js) se ofrece a recogerlo.
Flujo:
1. Das un código de recogida (authorization code)
• Pulsas «Iniciar sesión con Google» → página de autorización
• Tras consentir, Google devuelve un code temporal en la URL
2. Tu amigo va al punto (code + client_secret → access_token)
• El backend intercambia code + secret en Google
• Google verifica que la app es de confianza (client_secret)
3. Tras verificar, entregan el paquete (información del usuario)
• Google confirma → perfil transmitido → login exitoso
Puntos clave:
• Code de un solo uso, ~10 minutos
• client_secret solo en servidor
• access_token limitado en duración y permisos
Ventaja: no entregas tu contraseña a la app de terceros.
¿Qué es redirect_uri_mismatch?
El proveedor OAuth verifica la dirección de retorno; si no coincide, aparece este error.
Solución:
1. Configura redirect_uri en el panel del proveedor
2. Local: http://localhost:3000/api/auth/callback/google
3. Producción: https://tudominio.com/api/auth/callback/google
4. Declara ambos entornos
Errores frecuentes:
• Solo local, sin producción
• Barra de más o de menos
• http vs https
Comprobar:
• Ruta por defecto de NextAuth: /api/auth/callback/[provider]
• Coincidencia exacta con el panel del proveedor
La propagación puede tardar unos minutos.
¿Por qué WeChat es tan complicado?
1. Calificación empresarial
• Desarrolladores individuales excluidos
• Licencia y documentos
• Aprobación 1-3 días hábiles
2. Documentación
• Poco clara
• Mensajes de error vagos
• Depuración difícil
3. Depuración local
• Túnel obligatorio (ngrok, frp)
• Callback complejo
• Límites del entorno de prueba
4. Configuración
• Provider personalizado
• openid vs unionid
• Dominio de callback con registro ICP
Soluciones:
• Túnel en desarrollo
• Provider personalizado
• unionid como identificador único
• Paciencia con la aprobación
Consejo: prioriza Google o GitHub; WeChat como complemento si hace falta.
¿Cómo configurar un Provider personalizado?
```ts
import type { OAuthConfig, OAuthUserConfig } from 'next-auth/providers'
function WeChatProvider(options: OAuthUserConfig<WeChatProfile>): OAuthConfig<WeChatProfile> {
return {
id: 'wechat',
name: 'WeChat',
type: 'oauth',
authorization: {
url: 'https://open.weixin.qq.com/connect/qrconnect',
params: {
appid: options.clientId,
redirect_uri: options.callbackUrl,
response_type: 'code',
scope: 'snsapi_login',
state: 'state',
},
},
token: {
url: 'https://api.weixin.qq.com/sns/oauth2/access_token',
},
userinfo: {
url: 'https://api.weixin.qq.com/sns/userinfo',
},
profile(profile) {
return {
id: profile.openid,
name: profile.nickname,
email: null,
image: profile.headimgurl,
}
},
...options,
}
}
```
Puntos clave:
• URLs de authorization, token y userinfo
• Función profile
WeChat es complejo — consulta la documentación oficial o un Provider existente.
¿Qué diferencia hay entre unionid y openid?
• Identificador único en la app actual
• Distinto por aplicación
• Adecuado para una sola app
unionid:
• Identificador único bajo la misma cuenta Open Platform
• Igual para el mismo usuario en varias apps
• Adecuado para varias apps
Consejo:
• Una app → openid
• Varias apps → unionid
Ejemplo:
```ts
const response = await fetch(
`https://api.weixin.qq.com/sns/userinfo?access_token=${accessToken}&openid=${openid}`
)
const data = await response.json()
const unionid = data.unionid
```
unionid es mejor identificador estable; requiere autorización y Open Platform.
¿Cómo depurar WeChat en local?
Solución: túnel
1. ngrok:
```bash
ngrok http 3000
```
2. Dirección pública:
```
https://xxxxx.ngrok.io
```
3. Configuración:
• Open Platform: https://xxxxx.ngrok.io/api/auth/callback/wechat
• .env.local: NEXTAUTH_URL=https://xxxxx.ngrok.io
4. Prueba:
• Abre https://xxxxx.ngrok.io
• Login con WeChat
Notas:
• ngrok gratuito cambia la URL al reiniciar
• No uses ngrok en producción
• Tras pruebas: dirección de producción
Alternativa: frp propio, URL más estable.
12 min de lectura · Publicado el: 19 dic 2025 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Guía completa de OAuth en Next.js: Google, GitHub, WeChat y mejores prácticas
Explicamos OAuth 2.0 con palabras sencillas y te guiamos paso a paso para configurar inicio de sesión con Google, GitHub y WeChat en Next.js, con soluciones a redirect_uri_mismatch, riesgos de seguridad y errores habituales.
Parte 13 de 51
Siguiente
Guía completa de internacionalización en Next.js: mejores prácticas con next-intl
Explicación profunda de la internacionalización con App Router en Next.js: configuración completa de next-intl, diseño de rutas multilingües, mejores prácticas para gestionar archivos de traducción y ejemplos de código listos para usar
Parte 15 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario