Implementar login social con Claude Code: Next.js, Auth.js, Google y GitHub OAuth
Guía práctica para implementar login social seguro con Claude Code, Next.js/Auth.js, Google/GitHub OAuth y errores reales de producción.
El login social no es solo un botón para reducir el formulario de registro. Cuando dejas entrar a usuarios con Google o GitHub OAuth, también estás diseñando identidad, account linking, sesiones, cookies, CSRF, permisos del provider, soporte y auditoría. Si le pides a Claude Code “agrega login” sin más contexto, probablemente obtendrás una pantalla funcional, pero puedes dejar problemas serios: redirect URI mal configurada, vinculación automática con emails no verificados, scopes excesivos o client secrets dentro del código.
Esta guía usa Next.js App Router, Auth.js con el estilo de NextAuth v5, TypeScript y Prisma Adapter. Authorization code significa un código temporal que el servidor intercambia por tokens. state es un valor aleatorio para detectar CSRF. Redirect URI es la URL exacta a la que Google o GitHub devuelven al navegador. Account linking significa que un usuario ya autenticado decide conectar otra forma de login al mismo perfil.
Define primero el límite de seguridad
La primera pregunta no es cuántos providers soportar, sino qué debe probar el login. Para un SaaS, una herramienta interna, una comunidad técnica o una página de formación, Google y GitHub suelen ser suficientes. Google funciona bien para usuarios generales y correos corporativos; GitHub funciona bien para productos de desarrollo. Pedir permisos de Google Drive, Calendar o GitHub repo durante el login inicial suele bajar la confianza y la conversión.
Divide el trabajo para Claude Code en tareas pequeñas:
- Configurar providers de Auth.js, variables de entorno y callback route.
- Crear página de login y página protegida.
- Añadir solo
user.ida la sesión, nunca access tokens. - Permitir account linking solo desde una página de ajustes con el usuario ya autenticado.
- Impedir que el usuario elimine su último método de login.
- No escribir client secret, refresh token ni access token en código, logs, issues o borradores.
En una prueba de Masa, el problema principal no fue el código generado, sino una diferencia mínima entre la redirect URI en Google Cloud Console y el valor deAUTH_URL. Claude Code puede editar el repositorio, pero no sabe con certeza lo que configuraste en cada consola externa. Por eso conviene documentar URL, scopes, verificación de email y política de cookies antes de implementar.
Flujo OAuth en una vista
La guía de Google recomienda authorization code flow para aplicaciones web. El navegador recibe uncode temporal y el servidor lo intercambia con el provider. El client secret nunca debe llegar al navegador.
sequenceDiagram
participant User as User
participant App as Next.js app
participant Auth as Auth.js route
participant Provider as Google or GitHub
User->>App: Click "Continue with Google"
App->>Auth: signIn("google")
Auth->>Provider: Redirect with client_id, redirect_uri, scope, state
Provider->>User: Consent screen
Provider->>Auth: Redirect back with code and state
Auth->>Provider: Exchange code on the server
Provider->>Auth: Return tokens
Auth->>App: Create session cookie
App->>User: Show dashboard
state confirma que el callback pertenece a la solicitud iniciada por tu aplicación. Auth.js lo gestiona cuando usas sus rutas estándar. Si escribes un callback propio, Claude Code debe validar state. La documentación de GitHub también recomienda un state impredecible y abortar si no coincide.
Variables de entorno y scopes mínimos
El objetivo de esta configuración es autenticar, no pedir permisos de APIs innecesarias. Google usaopenid email profile; GitHub usaread:user user:email.
npm install next-auth@beta @auth/prisma-adapter prisma @prisma/client
npx prisma init
npm exec auth secret
# .env.local
AUTH_SECRET="valor generado por npm exec auth secret"
AUTH_URL="http://localhost:3000"
AUTH_GOOGLE_ID="client ID de Google Cloud Console"
AUTH_GOOGLE_SECRET="client secret de Google Cloud Console"
AUTH_GITHUB_ID="client ID de GitHub OAuth App"
AUTH_GITHUB_SECRET="client secret de GitHub OAuth App"
DATABASE_URL="postgresql://user:password@localhost:5432/app"
No publiques valores reales. El archivo de ejemplo debe quedar vacío.
# .env.example
AUTH_SECRET=
AUTH_URL=
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=
DATABASE_URL=
En Google, la redirect URI local debe serhttp://localhost:3000/api/auth/callback/google; en producción,https://example.com/api/auth/callback/google. En GitHub usahttps://example.com/api/auth/callback/github. Protocolo, dominio, ruta y provider deben coincidir exactamente.
Configuración de Auth.js
Este archivo rechaza Google siemail_verified no es verdadero y rechaza GitHub si no se obtiene email. También evita exponer tokens del provider en la sesión del cliente.
// auth.ts
import NextAuth, { type NextAuthConfig } from "next-auth";
import Google from "next-auth/providers/google";
import GitHub from "next-auth/providers/github";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
type GoogleProfile = {
sub: string;
name?: string;
email: string;
email_verified: boolean;
picture?: string;
};
export const authConfig = {
adapter: PrismaAdapter(prisma),
session: { strategy: "database" },
providers: [
Google({
authorization: {
params: {
scope: "openid email profile",
response_type: "code",
},
},
profile(profile: GoogleProfile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
emailVerified: profile.email_verified ? new Date() : null,
};
},
}),
GitHub({
authorization: {
params: {
scope: "read:user user:email",
},
},
}),
],
callbacks: {
async signIn({ account, profile, user }) {
if (account?.provider === "google") {
const googleProfile = profile as GoogleProfile | undefined;
return Boolean(googleProfile?.email && googleProfile.email_verified);
}
if (account?.provider === "github") {
return Boolean(user.email);
}
return true;
},
async session({ session, user }) {
session.user.id = user.id;
return session;
},
},
pages: {
signIn: "/login",
error: "/login",
},
} satisfies NextAuthConfig;
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig);
// src/types/next-auth.d.ts
import "next-auth";
declare module "next-auth" {
interface Session {
user: {
id: string;
name?: string | null;
email?: string | null;
image?: string | null;
};
}
}
// src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
Con Prisma Adapter, pide a Claude Code que añada también los modelos estándar User, Account, Session y VerificationToken. La tablaAccount es la base para auditar qué provider está vinculado a cada usuario.
Login y página protegida
Llamar asignIn desde una Server Action mantiene el flujo dentro de Auth.js y reduce errores de CSRF, cookies y callback.
// src/app/login/page.tsx
import { signIn } from "@/auth";
const providers = [
{ id: "google", label: "Continuar con Google" },
{ id: "github", label: "Continuar con GitHub" },
] as const;
export default function LoginPage({
searchParams,
}: {
searchParams: { error?: string };
}) {
return (
<main className="mx-auto flex min-h-screen max-w-sm flex-col justify-center gap-6 px-6">
<div>
<h1 className="text-2xl font-bold">Iniciar sesión</h1>
<p className="mt-2 text-sm text-gray-600">
Elige la cuenta de trabajo que usarás en esta aplicación.
</p>
</div>
{searchParams.error ? (
<p className="rounded-md bg-red-50 p-3 text-sm text-red-700">
No se pudo iniciar sesión. Prueba otra cuenta o contacta soporte.
</p>
) : null}
<div className="grid gap-3">
{providers.map((provider) => (
<form
key={provider.id}
action={async () => {
"use server";
await signIn(provider.id, { redirectTo: "/dashboard" });
}}
>
<button
type="submit"
className="w-full rounded-md border px-4 py-3 text-sm font-medium hover:bg-gray-50"
>
{provider.label}
</button>
</form>
))}
</div>
</main>
);
}
// src/app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
return (
<main className="mx-auto max-w-3xl p-8">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="mt-4 text-gray-700">
Signed in as {session.user.email}.
</p>
</main>
);
}
Account linking y eliminación segura
No vincules cuentas automáticamente solo porque el email coincida. El usuario debe estar autenticado y empezar el vínculo desde ajustes. Para eliminar un provider, valida origen y evita dejar al usuario sin ningún método de acceso.
// src/app/api/settings/linked-accounts/route.ts
import { auth } from "@/auth";
import { prisma } from "@/lib/prisma";
import { NextResponse } from "next/server";
function isSameOrigin(request: Request) {
const origin = request.headers.get("origin");
const host = request.headers.get("host");
if (!origin || !host) return false;
try {
return new URL(origin).host === host;
} catch {
return false;
}
}
export async function DELETE(request: Request) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!isSameOrigin(request)) {
return NextResponse.json({ error: "Bad origin" }, { status: 403 });
}
const body = (await request.json()) as { provider?: string };
const accounts = await prisma.account.findMany({
where: { userId: session.user.id },
select: { provider: true },
});
if (accounts.length <= 1) {
return NextResponse.json(
{ error: "No puedes eliminar el último método de login." },
{ status: 400 },
);
}
await prisma.account.deleteMany({
where: { userId: session.user.id, provider: body.provider },
});
return NextResponse.json({ ok: true });
}
Prompt para Claude Code y revisión
Agrega login social con Google y GitHub a esta app Next.js App Router usando Auth.js v5.
Requisitos:
- Leer client secrets solo desde .env.local o secretos del despliegue
- Google scope debe ser openid email profile
- GitHub scope debe ser read:user user:email
- Permitir Google solo si email_verified es true
- Agregar solo user.id a la sesión y no exponer access_token al cliente
- Account linking solo desde ajustes con usuario autenticado
- No permitir eliminar el último método de login
- Al final, reportar lint y pasos de prueba OAuth manual
| Revisión | Qué comprobar |
|---|---|
| OAuth flow | Se usa authorization code flow y la redirect URI coincide con el provider |
| state y CSRF | Auth.js mantiene rutas estándar y las APIs propias validan origen |
| Cookie y sesión | Tokens del provider quedan en servidor; cookies de producción consideran Secure, HttpOnly y SameSite |
| Account linking | Email verificado y acción explícita del usuario antes de vincular |
Casos de uso reales
Primero, un panel B2B SaaS puede usar Google como vía principal y añadir después restricciones de dominio o RBAC. GitHub puede quedar para usuarios técnicos.
Segundo, una herramienta para desarrolladores puede usar GitHub para onboarding y pedir permisos de repositorio solo cuando el usuario entra a una función que los necesita.
Tercero, un producto con email y contraseña puede añadir login social pidiendo al usuario autenticado que vincule Google o GitHub desde ajustes. Eso evita fusiones accidentales.
Cuarto, una página de formación o webinars puede reducir fricción de registro y usar email verificado de Google para seguimiento y soporte.
Fallos habituales en producción
La redirect URI incorrecta es el fallo más común. Si local funciona y producción no, comparaAUTH_URL, cabeceras Host del proxy y callback URL en Google/GitHub.
La vinculación automática por email es peligrosa. Google devuelveemail_verified, pero no todos los providers ofrecen la misma garantía. El mismo string de email no basta como prueba.
Los scopes excesivos dañan PV y consultas. Si el usuario solo quiere entrar y ve permisos de repositorio, se irá. Separa login de autorizaciones avanzadas.
Un client secret escrito en código debe bloquear la revisión. Usa.env.local, secretos de despliegue o secret manager.
Los refresh tokens de Google también requieren diseño. Google puede devolverlos solo en el primer consentimiento. Para login puro no los guardes; para APIs en segundo plano crea una tarea separada de rotación.
Referencias oficiales y enlaces internos
Consulta fuentes primarias: Auth.js, Google Identity Services OAuth, GitHub OAuth Apps y OWASP Authentication Cheat Sheet.
Para ampliar, revisa implementación OAuth con Claude Code, autenticación JWT, buenas prácticas de seguridad y gestión de variables de entorno.
Consultoría y puntos de verificación
ClaudeCodeLab puede ayudar a convertir un botón de login en un flujo de autenticación revisable: requisitos, tareas para Claude Code, revisión de código, manejo de secretos y pruebas OAuth manuales. Si quieres aumentar consultas o conversiones, empieza por definir usuario objetivo, provider, permisos mínimos y mensajes de error.
Al probar este artículo, verifica que las redirect URI de desarrollo y producción coincidan exactamente, que Googleemail_verified se valide, que GitHub sin email público tenga un error claro, que state y cookies sigan el flujo estándar de Auth.js, que no se pueda eliminar el último método de login y que ningún client secret aparezca en código o logs.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.