🔐 GitHub OAuth

Next.js + Strapi

Autenticación social moderna paso a paso

📋 Agenda

  • ¿Por qué OAuth?
  • Flujo de autenticación
  • Arquitectura del proyecto
  • Implementación paso a paso
  • Seguridad y mejores prácticas

🤔 ¿Por qué OAuth?

  • Los usuarios odian crear cuentas
  • Menos fricción = más conversiones
  • GitHub = identidad de desarrolladores
  • Delegamos la seguridad al experto

🔑 OAuth ≠ Autenticación

OAuth 2.0 es un protocolo de autorización

  • Autenticación: ¿Quién eres?
  • Autorización: ¿Qué puedes hacer?

Usamos la autorización para obtener datos y autenticar

🏗️ Arquitectura

Next.js (3000) → Strapi (1337) → GitHub


  • Frontend: UI + Route Handler
  • Backend: Strapi gestiona OAuth
  • Provider: GitHub OAuth App

🔄 Flujo de Auth

  1. Usuario click → "Sign in with GitHub"
  2. Redirect → Strapi /api/connect/github
  3. Strapi → GitHub login page
  4. GitHub → Strapi con código
  5. Strapi intercambia código por token
  6. Strapi → Frontend con access_token
  7. Frontend → Strapi pide JWT
  8. JWT guardado en cookie HttpOnly

💻 Botón de Login

// src/app/page.tsx
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
const url = new URL(backendUrl + "/api/connect/github");

<Link href={url.href}>
  Sign in with GitHub
</Link>

Redirigimos a Strapi, no directamente a GitHub

💻 Route Handler

// src/app/connect/[provider]/redirect/route.ts
export async function GET(request, { params }) {
  const token = searchParams.get("access_token");
  
  // Intercambiar por JWT de Strapi
  const res = await fetch(
    `${backendUrl}/api/auth/${provider}/callback?access_token=${token}`
  );
  const data = await res.json();
  
  // Guardar en cookie segura
  cookieStore.set("jwt", data.jwt, config);
  
  return NextResponse.redirect("/dashboard");
}

🛡️ Cookies Seguras

const config = {
  maxAge: 60 * 60 * 24 * 7,  // 1 semana
  httpOnly: true,             // No JS access
  secure: true,               // Solo HTTPS
  path: "/",
  domain: "localhost"
};

httpOnly previene robo de tokens vía XSS

🚧 Middleware de Protección

// src/middleware.ts
export async function middleware(request) {
  const user = await getUserMeLoader();
  
  if (request.nextUrl.pathname.startsWith("/dashboard") 
      && user.ok === false) {
    return NextResponse.redirect("/");
  }
  
  return NextResponse.next();
}

❓ ¿Por qué verificar con Strapi?

La cookie podría existir pero...

  • El token expiró en el servidor
  • El usuario fue deshabilitado
  • El token fue revocado

Strapi es la fuente de verdad

⚡ Server Actions para Logout

async function logoutAction() {
  "use server";
  
  const cookieStore = await cookies();
  cookieStore.set("jwt", "", { ...config, maxAge: 0 });
  
  redirect("/");
}

Sin API routes adicionales

⚙️ Configuración Strapi

  1. Admin → Settings → Providers
  2. Habilitar GitHub
  3. Client ID + Client Secret
  4. Redirect URL del frontend
Redirect: http://localhost:3000/connect/github/redirect

🐙 GitHub OAuth App

Settings → Developer Settings → OAuth Apps

  • Homepage: http://localhost:3000
  • Callback: http://localhost:1337/api/connect/github/callback

El callback apunta a Strapi, no al frontend

🔌 Extensibilidad

El segmento dinámico [provider] permite:

  • /connect/google/redirect
  • /connect/twitter/redirect
  • /connect/discord/redirect

Solo configura el provider en Strapi

✅ Mejores Prácticas

  • Nunca exponer Client Secret en frontend
  • HTTPS obligatorio en producción
  • No cachear datos de autenticación
  • Implementar rate limiting
  • Manejar errores gracefully

📁 Estructura

frontend/src/
├── app/
│   ├── page.tsx           # Login
│   ├── dashboard/         # Protected
│   └── connect/[provider]/redirect/
├── components/
│   └── LogoutButton.tsx
├── services/
│   └── user-me-loader.ts
└── middleware.ts

🎬 Demo Time

Frontend: http://localhost:3000

Backend: http://localhost:1337

📚 Recursos

  • Strapi Providers Docs
  • Next.js Route Handlers
  • GitHub OAuth Documentation
  • OAuth 2.0 RFC 6749

🙏 ¡Gracias!

¿Preguntas?


github.com/tu-usuario/social-strapi