OAuth con Google y GitHub en Supabase

OAuth (Open Authorization) es un protocolo que permite a tus usuarios iniciar sesión usando su cuenta de Google, GitHub u otro proveedor externo, sin tener que crear una contraseña nueva en tu app.

El flujo funciona así: tu app redirige al usuario a Google (o GitHub), el usuario autoriza el acceso, y el proveedor redirige de vuelta a tu app con un código de autorización. Supabase se encarga de intercambiar ese código por una sesión.

Por qué usar OAuth

  • Menos fricción: El usuario no tiene que recordar otra contraseña
  • Verificación automática: El email ya está verificado por el proveedor
  • Datos del perfil: Puedes obtener nombre, foto y email directamente
  • Seguridad delegada: Google/GitHub manejan la seguridad de las credenciales

Configurar Google OAuth

Paso 1: Crear credenciales en Google Cloud

  1. Ve a console.cloud.google.com
  2. Crea un proyecto nuevo o selecciona uno existente
  3. Ve a APIs & Services > Credentials
  4. Haz clic en Create Credentials > OAuth client ID
  5. Selecciona Web application como tipo
  6. En Authorized redirect URIs, agrega:
plaintext
https://<tu-proyecto>.supabase.co/auth/v1/callback
Donde encuentro la redirect URI

En el Dashboard de Supabase: Authentication > Providers > Google. Ahi aparece la Callback URL exacta que debes agregar en Google Cloud.

  1. Guarda y copia el Client ID y Client Secret

Paso 2: Configurar la pantalla de consentimiento

En Google Cloud, ve a APIs & Services > OAuth consent screen:

  1. Selecciona External como tipo de usuario
  2. Llena el nombre de la app, email de soporte y logo
  3. En Scopes, agrega email y profile
  4. Agrega tu dominio en Authorized domains
  5. Pública la app (mientras pruebas, puedes dejarla en modo Testing)

Paso 3: Configurar en Supabase

  1. Ve al Dashboard de Supabase: Authentication > Providers > Google
  2. Activa el provider
  3. Pega el Client ID y Client Secret de Google Cloud
  4. Guarda los cambios

Configurar GitHub OAuth

Paso 1: Crear una OAuth App en GitHub

  1. Ve a github.com/settings/developers
  2. Haz clic en New OAuth App
  3. Llena los campos:
    • Application name: El nombre de tu app
    • Homepage URL: La URL de tu app (por ejemplo, https://tudominio.com)
    • Authorization callback URL:
plaintext
https://<tu-proyecto>.supabase.co/auth/v1/callback
  1. Haz clic en Register application
  2. Copia el Client ID
  3. Genera un Client Secret y copialo

Paso 2: Configurar en Supabase

  1. Ve al Dashboard de Supabase: Authentication > Providers > GitHub
  2. Activa el provider
  3. Pega el Client ID y Client Secret de GitHub
  4. Guarda los cambios

Implementar signInWithOAuth

Una vez configurados los proveedores, el código es idéntico para cualquiera de ellos:

typescript
import { createClient } from '@/lib/supabase/client'
 
const supabase = createClient()
 
// Login con Google
async function loginConGoogle() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: `${window.location.origin}/auth/callback`
    }
  })
 
  if (error) {
    console.error('Error:', error.message)
  }
  // Si no hay error, el navegador se redirige automaticamente a Google
}
 
// Login con GitHub
async function loginConGitHub() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'github',
    options: {
      redirectTo: `${window.location.origin}/auth/callback`
    }
  })
 
  if (error) {
    console.error('Error:', error.message)
  }
}
redirectTo es importante

La URL en redirectTo es donde Supabase redirige después de que el usuario se autentica con el proveedor. Esta URL debe estar registrada en Authentication > URL Configuration > Redirect URLs en el Dashboard de Supabase.

Solicitar permisos adicionales (scopes)

Por defecto, Supabase pide los scopes minimos (email y profile). Si necesitas acceso adicional:

typescript
// Ejemplo: acceso a repos privados de GitHub
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: `${window.location.origin}/auth/callback`,
    scopes: 'repo read:user'
  }
})
 
// Ejemplo: acceso al Google Calendar
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: `${window.location.origin}/auth/callback`,
    scopes: 'https://www.googleapis.com/auth/calendar.readonly'
  }
})

Redirect URLs y callback

Configurar Redirect URLs

En el Dashboard de Supabase: Authentication > URL Configuration:

  • Site URL: La URL principal de tu app (ejemplo: https://tudominio.com)
  • Redirect URLs: Agrega todas las URLs validas a las que Supabase puede redirigir. Incluye:
    • http://localhost:3000/auth/callback (desarrollo)
    • https://tudominio.com/auth/callback (producción)
Wildcards

Puedes usar wildcards en las Redirect URLs. Por ejemplo, https://*.tudominio.com/auth/callback permite subdominios. Esto es útil si usas preview deployments en Vercel.

Ruta de callback

Necesitas una ruta en tu app que procese el código de autorización después del redirect:

typescript
// app/auth/callback/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
 
export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')
  const next = searchParams.get('next') ?? '/dashboard'
 
  if (code) {
    const supabase = await createClient()
    const { error } = await supabase.auth.exchangeCodeForSession(code)
 
    if (!error) {
      return NextResponse.redirect(`${origin}${next}`)
    }
  }
 
  return NextResponse.redirect(`${origin}/login?error=oauth`)
}

Esta ruta es la misma que usamos para la confirmación de email. Un solo endpoint maneja todos los flujos de auth que usan códigos de autorización.

Obtener datos del perfil del proveedor

Después de que el usuario se autentica con OAuth, sus datos del proveedor están disponibles en user_metadata:

typescript
const { data: { user } } = await supabase.auth.getUser()
 
if (user) {
  // Datos comunes en todos los proveedores
  console.log('Email:', user.email)
  console.log('ID:', user.id)
 
  // Datos especificos del proveedor
  const metadata = user.user_metadata
  console.log('Nombre:', metadata.full_name || metadata.name)
  console.log('Avatar:', metadata.avatar_url || metadata.picture)
  console.log('Proveedor:', user.app_metadata.provider)
}

Estructura de metadata por proveedor

Google devuelve:

typescript
user.user_metadata = {
  avatar_url: 'https://lh3.googleusercontent.com/...',
  email: 'usuario@gmail.com',
  email_verified: true,
  full_name: 'Juan Perez',
  iss: 'https://accounts.google.com',
  name: 'Juan Perez',
  picture: 'https://lh3.googleusercontent.com/...',
  provider_id: '1234567890',
  sub: '1234567890'
}

GitHub devuelve:

typescript
user.user_metadata = {
  avatar_url: 'https://avatars.githubusercontent.com/...',
  email: 'usuario@ejemplo.com',
  email_verified: true,
  full_name: 'juanperez',
  name: 'Juan Perez',
  preferred_username: 'juanperez',
  provider_id: '12345678',
  sub: '12345678',
  user_name: 'juanperez'
}

Componente completo de login social

typescript
'use client'
 
import { createClient } from '@/lib/supabase/client'
 
export function SocialLogin() {
  const supabase = createClient()
 
  const handleOAuthLogin = async (provider: 'google' | 'github') => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider,
      options: {
        redirectTo: `${window.location.origin}/auth/callback`
      }
    })
 
    if (error) {
      console.error(`Error con ${provider}:`, error.message)
    }
  }
 
  return (
    <div className="space-y-3">
      <button
        onClick={() => handleOAuthLogin('google')}
        className="w-full flex items-center justify-center gap-2
                   py-2 px-4 border border-gray-600 rounded-md
                   hover:bg-gray-800 transition-colors"
      >
        <GoogleIcon />
        Continuar con Google
      </button>
 
      <button
        onClick={() => handleOAuthLogin('github')}
        className="w-full flex items-center justify-center gap-2
                   py-2 px-4 border border-gray-600 rounded-md
                   hover:bg-gray-800 transition-colors"
      >
        <GitHubIcon />
        Continuar con GitHub
      </button>
    </div>
  )
}
 
// Componentes de iconos simplificados
function GoogleIcon() {
  return (
    <svg className="w-5 h-5" viewBox="0 0 24 24">
      <path fill="currentColor" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" />
      <path fill="currentColor" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
      <path fill="currentColor" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
      <path fill="currentColor" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
    </svg>
  )
}
 
function GitHubIcon() {
  return (
    <svg className="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
      <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
    </svg>
  )
}

Vincular multiples proveedores a una cuenta

Si un usuario se registra con email y después quiere vincular su cuenta de Google, Supabase maneja esto automáticamente si el email coincide. Puedes configurar este comportamiento en el Dashboard:

Authentication > Providers > Email > Auto-confirm when signing in with OAuth providers with a verified email

Cuando está opción está activada y un usuario hace OAuth con el mismo email que uso para registrarse con contraseña, Supabase vincula ambos métodos de autenticación a la misma cuenta.

Cuidado con la vinculación automática

La vinculación automática por email puede ser un riesgo de seguridad si un proveedor OAuth no verifica emails correctamente. Para la mayoría de apps con Google y GitHub esto es seguro, ya que ambos verifican emails. Evalua el riesgo si agregas proveedores menos conocidos.

Proveedores soportados por Supabase

Además de Google y GitHub, Supabase soporta:

ProveedorProvider string
Googlegoogle
GitHubgithub
Appleapple
Discorddiscord
Twitter/Xtwitter
Facebookfacebook
Azure (Microsoft)azure
GitLabgitlab
Bitbucketbitbucket
Slackslack
Spotifyspotify
Twitchtwitch
LinkedIn (OIDC)linkedin_oidc

La configuración sigue el mismo patrón para todos: crear credenciales en el proveedor, agregar la callback URL de Supabase, y pegar Client ID + Client Secret en el Dashboard.