--- id: ADR-0010 type: ADR title: "OAuth Social Strategy" status: Accepted decision_date: 2026-01-10 updated_at: 2026-01-10 simco_version: "4.0.1" stakeholders: - "Equipo MiChangarrito" tags: - 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: 1. Que proveedores soportar 2. Como integrar con nuestro sistema de auth existente 3. 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:** - Control absoluto - **Cons:** - Mucho trabajo - Propenso a errores de seguridad - Mantener cambios de API --- ## Consecuencias ### Positivas 1. **Control:** Logica de negocio en nuestro codigo 2. **Flexibilidad:** Agregar providers facilmente 3. **Integracion:** Se integra con nuestro JWT flow 4. **Costo:** Sin gastos adicionales ### Negativas 1. **Mantenimiento:** Actualizar strategies 2. **Configuracion:** Setup en consolas de providers --- ## Implementacion ### Modelo de Datos ```sql 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 ```typescript async findOrCreateFromOAuth(profile: OAuthProfile): Promise { // 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 ```typescript 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 ```typescript 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) ```typescript const [request, response, promptAsync] = Google.useAuthRequest({ clientId: GOOGLE_CLIENT_ID, iosClientId: GOOGLE_IOS_CLIENT_ID, androidClientId: GOOGLE_ANDROID_CLIENT_ID, }); ``` ### Apple (iOS) ```typescript import * as AppleAuthentication from 'expo-apple-authentication'; const credential = await AppleAuthentication.signInAsync({ requestedScopes: [ AppleAuthentication.AppleAuthenticationScope.FULL_NAME, AppleAuthentication.AppleAuthenticationScope.EMAIL, ], }); ``` --- ## Referencias - [Passport.js](http://www.passportjs.org/) - [Google Identity](https://developers.google.com/identity) - [Sign in with Apple](https://developer.apple.com/sign-in-with-apple/) - [INT-012: OAuth Social](../02-integraciones/INT-012-oauth-social.md) - [MCH-030: Auth Social](../01-epicas/MCH-030-auth-social.md) --- **Fecha decision:** 2026-01-10 **Autores:** Architecture Team