El inicio de sesión social es esperado en toda aplicación web moderna. Los usuarios quieren hacer clic en "Continuar con Google" y autenticarse en segundos, sin crear otra contraseña. Los desarrolladores quieren ofrecer esto, pero la implementación es notoriamente compleja: códigos de autorización OAuth2, URIs de redirección, parámetros de estado, verificadores de código PKCE, intercambios de tokens y peculiaridades específicas de cada proveedor.
En una aplicación Node.js, implementar Google OAuth requiere passport, passport-google-oauth20, configuración de sesiones, rutas de callback y configuración específica del proveedor. GitHub requiere passport-github2 con diferentes alcances y diferentes campos de perfil de usuario. Cada proveedor es un paquete separado con su propia API.
FLIN proporciona funciones integradas para seis proveedores OAuth: Google, GitHub, Discord, Apple, LinkedIn y Telegram. Cada proveedor sigue el mismo patrón de tres pasos: generar la URL de autenticación, manejar el callback y crear o iniciar sesión del usuario.
El patrón universal
Cada proveedor OAuth en FLIN sigue el mismo flujo:
Paso 1: Generar URL de autenticación (página de login)
auth = auth_PROVIDER_login(baseUrl)
session.PROVIDERState = auth.state
<a href={auth.url}>Continue with PROVIDER</a>
Paso 2: Manejar callback (página de callback)
result = auth_PROVIDER_callback(query.code, query.state, session.PROVIDERState)
session.PROVIDERState = none
Paso 3: Encontrar o crear usuario
if result.ok {
existing = User.where(email == result.user.email).first
if existing != none { iniciar sesión }
else { crear nuevo usuario }
}Los nombres de funciones, el patrón de almacenamiento en sesión y la lógica de encontrar-o-crear son idénticos en todos los proveedores. Aprenda uno y los conocerá todos.
Google OAuth con PKCE
Google es el proveedor OAuth más utilizado. FLIN lo implementa con PKCE (Proof Key for Code Exchange), la extensión de seguridad recomendada para clientes públicos:
flin// app/login.flin -- Paso 1: Generar URL de autenticación
baseUrl = env("BASE_URL") || "http://localhost:3000"
googleAuth = auth_google_login(baseUrl)
// Almacenar estado y verificador PKCE en sesión
session.googleState = googleAuth.state
session.googleRedirectUri = googleAuth.redirect_uri
session.googleCodeVerifier = googleAuth.code_verifier
// Renderizar el botón de login
<a href={googleAuth.url} class="social-btn">
Continue with Google
</a>La función auth_google_login() genera:
- Un parámetro state aleatorio (protección CSRF)
- Un code_verifier y code_challenge (PKCE)
- La URL de autorización completa con alcances, URI de redirección y tipo de respuesta
flin// app/auth/google/callback.flin -- Paso 2: Manejar callback
layout = "auth"
result = auth_google_callback(query.code, query.state, session.googleState)
session.googleState = none
authOk = false
fn processGoogleAuth() {
if result.ok {
existing = User.where(email == result.user.email && role == "User").first
if existing != none {
session.user = existing.email
session.userName = existing.name || result.user.name
session.userId = to_text(existing.id)
} else {
newUser = User {
email: result.user.email,
name: result.user.name,
provider: "Google",
providerId: to_text(result.user.id),
avatar: result.user.avatar || "",
emailVerified: true
}
save newUser
session.user = newUser.email
session.userName = newUser.name
session.userId = to_text(newUser.id)
}
authOk = true
}
}
processGoogleAuth()
{if authOk}
<h2>Welcome!</h2>
<script>setTimeout(function() { window.location.href = "/tasks"; }, 1000);</script>
{else}
<h2>Authentication failed</h2>
<a href="/login">Try again</a>
{/if}El objeto de resultado OAuth
Cada función auth_*_callback() retorna la misma estructura:
flin// Éxito
result.ok // true
result.user.id // ID de usuario del proveedor
result.user.email // Email (puede ser none para algunos proveedores)
result.user.name // Nombre para mostrar
result.user.avatar // URL de imagen de perfil
result.user.provider // "google", "github", etc.
// Fallo
result.ok // false
result.error // Descripción del errorEsta interfaz consistente significa que la lógica de encontrar-o-crear es idéntica para cada proveedor. Las únicas diferencias están en los nombres de variables de sesión y la configuración de la página de login.
Seguridad: estado y PKCE
Cada flujo OAuth en FLIN está protegido por dos mecanismos:
Parámetro de estado. Una cadena aleatoria almacenada en la sesión e incluida en la URL de autorización. El callback verifica que el estado en la respuesta coincida con el estado en la sesión. Esto previene ataques CSRF donde un atacante engaña a un usuario para que se autentique con la cuenta del atacante.
PKCE (Proof Key for Code Exchange). Se genera un verificador de código aleatorio y su hash SHA-256 (el desafío de código) se envía con la solicitud de autorización. Al intercambiar el código de autorización por tokens, se envía el verificador de código original. El servidor de autorización verifica que el hash coincida. Esto previene ataques de intercepción de códigos de autorización.
rustfn generate_pkce() -> (String, String) {
let verifier = generate_random_string(64);
let challenge = base64url_encode(&sha256(verifier.as_bytes()));
(verifier, challenge)
}Ambas protecciones se generan y verifican automáticamente por las funciones auth_<em>_login() y auth_</em>_callback(). El desarrollador almacena el estado y el verificador en la sesión pero nunca necesita entender por qué.
Por qué importa el OAuth integrado
OAuth2 es un estándar, pero los detalles de implementación varían enormemente entre proveedores. Google requiere PKCE. Apple envía un POST. GitHub podría no retornar un email. Discord usa nombres de alcance diferentes. LinkedIn ha deprecado múltiples versiones de API.
Al absorber estas diferencias en funciones integradas, FLIN asegura que: 1. Cada implementación OAuth sigue el estándar correctamente. 2. Las medidas de seguridad (estado, PKCE) siempre se aplican. 3. Las peculiaridades del proveedor se manejan internamente. 4. La interfaz del desarrollador es consistente en todos los proveedores.
La alternativa -- instalar una biblioteca separada para cada proveedor, configurar cada una de manera diferente y esperar que todas manejen la seguridad correctamente -- es exactamente el tipo de complejidad que FLIN fue diseñado para eliminar.
En el próximo artículo, cubrimos la autenticación OTP por WhatsApp -- el método de autenticación diseñado específicamente para el mercado africano donde la identidad basada en teléfono es más común que el email.
Esta es la Parte 111 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abidjan y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.
Navegación de la serie: - [110] Autenticación de dos factores (TOTP) - [111] OAuth2 y autenticación social (estás aquí) - [112] Autenticación OTP por WhatsApp para África - [113] Validadores de cuerpo de solicitud