| id |
type |
title |
status |
decision_date |
updated_at |
simco_version |
stakeholders |
tags |
| ADR-0010 |
ADR |
OAuth Social Strategy |
Accepted |
2026-01-10 |
2026-01-10 |
4.0.1 |
|
| oauth |
| authentication |
| google |
| apple |
| passport |
|
ADR-0010: OAuth Social Strategy
Metadata
| Campo |
Valor |
| ID |
ADR-0010 |
| Estado |
Accepted |
| Fecha |
2026-01-10 |
| Autor |
Architecture Team |
| Supersede |
- |
Contexto
MiChangarrito quiere permitir registro e inicio de sesion con cuentas sociales (Google, Apple) para reducir friccion de onboarding. Necesitamos decidir:
- Que proveedores soportar
- Como integrar con nuestro sistema de auth existente
- Como manejar vinculacion de cuentas
Decision
Implementamos OAuth 2.0 con Passport.js para Google y Apple, con tabla separada oauth_accounts vinculada a users.
- Google: Principal proveedor social
- Apple: Requerido para iOS App Store
- Passport.js: Strategies bien mantenidas
Alternativas Consideradas
Opcion 1: Auth0/Firebase Auth
- Pros:
- Todo manejado
- Multiples providers
- Cons:
- Costo adicional
- Dependencia externa
- Menos control
Opcion 2: Passport.js (Elegida)
- Pros:
- Open source
- Bien documentado
- Control total
- Sin costos adicionales
- Cons:
- Mas trabajo de implementacion
- Mantener actualizaciones
Opcion 3: Implementacion manual
- Pros:
- Cons:
- Mucho trabajo
- Propenso a errores de seguridad
- Mantener cambios de API
Consecuencias
Positivas
- Control: Logica de negocio en nuestro codigo
- Flexibilidad: Agregar providers facilmente
- Integracion: Se integra con nuestro JWT flow
- Costo: Sin gastos adicionales
Negativas
- Mantenimiento: Actualizar strategies
- Configuracion: Setup en consolas de providers
Implementacion
Modelo de Datos
CREATE TABLE auth.oauth_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) NOT NULL,
provider VARCHAR(20) NOT NULL, -- google, apple
provider_user_id VARCHAR(255) NOT NULL,
email VARCHAR(255),
name VARCHAR(255),
avatar_url TEXT,
access_token TEXT,
refresh_token TEXT,
expires_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(provider, provider_user_id)
);
Flujo de Autenticacion
1. Usuario hace clic en "Continuar con Google"
2. Redirect a Google OAuth
3. Usuario autoriza
4. Callback a nuestro servidor con code
5. Intercambiar code por tokens
6. Obtener perfil de usuario
7. Buscar/crear usuario en nuestra BD
8. Generar JWT nuestro
9. Redirect a frontend con JWT
Casos de Vinculacion
| Escenario |
Accion |
| Nuevo usuario, nuevo email |
Crear user + oauth_account |
| Email existe, mismo provider |
Error (ya vinculado) |
| Email existe, otro provider |
Ofrecer vincular cuentas |
| Usuario logueado vincula nueva |
Agregar oauth_account |
Codigo de Vinculacion
async findOrCreateFromOAuth(profile: OAuthProfile): Promise<User> {
// 1. Buscar oauth_account existente
const existingOAuth = await this.oauthRepo.findOne({
where: {
provider: profile.provider,
providerUserId: profile.id,
},
});
if (existingOAuth) {
return existingOAuth.user;
}
// 2. Buscar user por email
const existingUser = await this.userRepo.findOne({
where: { email: profile.email },
});
if (existingUser) {
// Vincular automaticamente si email verificado en provider
if (profile.emailVerified) {
await this.createOAuthAccount(existingUser, profile);
return existingUser;
}
// Sino, pedir confirmacion
throw new EmailExistsError(profile.email, profile.provider);
}
// 3. Crear nuevo usuario
const user = await this.createUser({
email: profile.email,
name: profile.name,
avatarUrl: profile.avatar,
emailVerified: profile.emailVerified,
});
await this.createOAuthAccount(user, profile);
return user;
}
Providers Soportados
Google
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback',
scope: ['profile', 'email'],
}, verify));
Apple
passport.use(new AppleStrategy({
clientID: process.env.APPLE_CLIENT_ID,
teamID: process.env.APPLE_TEAM_ID,
keyID: process.env.APPLE_KEY_ID,
privateKeyString: process.env.APPLE_PRIVATE_KEY,
callbackURL: '/auth/apple/callback',
scope: ['name', 'email'],
}, verify));
Mobile
Google (Expo)
const [request, response, promptAsync] = Google.useAuthRequest({
clientId: GOOGLE_CLIENT_ID,
iosClientId: GOOGLE_IOS_CLIENT_ID,
androidClientId: GOOGLE_ANDROID_CLIENT_ID,
});
Apple (iOS)
import * as AppleAuthentication from 'expo-apple-authentication';
const credential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL,
],
});
Referencias
Fecha decision: 2026-01-10
Autores: Architecture Team