This project was generated using Angular CLI version 20.3.6.
To start a local development server, run:
ng serveOnce the server is running, open your browser and navigate to http://localhost:4200/. The application will automatically reload whenever you modify any of the source files.
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
ng generate component component-nameFor a complete list of available schematics (such as components, directives, or pipes), run:
ng generate --helpTo build the project run:
ng buildThis will compile your project and store the build artifacts in the dist/ directory. By default, the production build optimizes your application for performance and speed.
To execute unit tests with the Karma test runner, use the following command:
ng testFor end-to-end (e2e) testing, run:
ng e2eAngular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
For more information on using the Angular CLI, including detailed command references, visit the Angular CLI Overview and Command Reference page.
- Introducción: ¿Qué "Sabor" de Cognito Usamos?
- Arquitectura General
- Flujo de Registro y Confirmación (PIN)
- Flujo de Login
- Componentes Principales
- Diagramas de Flujo Detallados
- Configuración de AWS Cognito
NO usamos AWS Amplify Framework completo (el que tiene Auth.signIn(), Auth.currentAuthenticatedUser(), etc.)
Usamos una implementación HÍBRIDA que combina:
import {
signIn, // ❌ Solo para registro username/password
signUp, // ✅ Para registro de nuevos usuarios
confirmSignUp, // ✅ Para confirmar código de verificación
resendSignUpCode, // ✅ Para reenviar código de confirmación
signOut, // ✅ Para cerrar sesión
// ...otros métodos específicos
} from 'aws-amplify/auth';Características:
- ✅ Módulo específico de autenticación (no todo el framework)
- ✅ Versión 6 (más moderna y modular)
- ✅ Solo se usa para operaciones de registro y gestión de cuentas
- ❌ NO se usa para el flujo OAuth principal
El flujo de login principal está implementado MANUALMENTE usando:
// 1. OAuth Service personalizado (manual)
export class OAuthService {
// Genera PKCE, construye URLs, maneja callbacks
async login() { /* Implementación manual del flujo OAuth */ }
// No usa métodos de Amplify, usa fetch() directo
async exchangeCodeForTokens(code: string) {
const response = await fetch(`${domain}/oauth2/token`, {...});
}
}Características:
- ✅ OAuth 2.0 Authorization Code Flow con PKCE
- ✅ Cognito Hosted UI (página web de login de AWS)
- ✅ Implementación manual (sin biblioteca OAuth)
- ✅ Tokens guardados en localStorage
- ✅ Capacitor Browser para abrir el Hosted UI
┌─────────────────────────────────────────────────────┐
│ ARQUITECTURA HÍBRIDA DE COGNITO │
├─────────────────────────────────────────────────────┤
│ │
│ 📦 aws-amplify/auth (v6) │
│ └─ SOLO para: signUp, confirmSignUp, │
│ resendCode, forgotPassword │
│ │
│ 🔐 OAuth Service (MANUAL) │
│ └─ Para: Login, Logout, Refresh Token │
│ └─ Usando: Hosted UI + PKCE │
│ └─ Sin biblioteca OAuth externa │
│ │
│ 🌐 Cognito Hosted UI │
│ └─ Página web de AWS para autenticación │
│ │
│ 📱 Capacitor Browser │
│ └─ Para abrir el Hosted UI en el móvil │
│ │
└─────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ FRONTEND (Ionic/Angular) │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ AuthService │ │ OAuthService │ │
│ │ (Amplify v6) │ │ (Manual PKCE) │ │
│ └───────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ signUp() │ login() → Hosted UI │
│ │ confirmSignUp() │ exchangeTokens() │
│ │ resendCode() │ refreshToken() │
│ │ │ │
└──────────┼─────────────────────┼─────────────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ AWS COGNITO USER POOL │
├──────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ User Pool │ │ Hosted UI │ │
│ │ - Usuarios │ │ - Login Page │ │
│ │ - Atributos │ │ - OAuth 2.0 Endpoints │ │
│ │ - Policies │ │ - /oauth2/authorize │ │
│ │ │ │ - /oauth2/token │ │
│ └──────────────────┘ └──────────────────────────┘ │
│ │
│ ✉️ Email Verification: AWS SES │
│ └─ Envía códigos de 6 dígitos automáticamente │
│ │
└──────────────────────────────────────────────────────────┘
{
"dependencies": {
"aws-amplify": "^6.x.x", // ← Módulo de autenticación
"@aws-sdk/client-cognito-identity-provider": "^3.x.x",
"crypto-js": "^4.x.x", // ← Para PKCE (SHA256)
"@capacitor/browser": "^6.x.x", // ← Para abrir Hosted UI
"@capacitor/app": "^6.x.x" // ← Para deep links
}
}// register.component.ts
async register() {
const registerData: RegisterRequest = {
name: this.name,
email: this.email,
school: this.school,
age: this.age,
gender: this.gender,
phone: this.phone,
password: this.password
};
// Llamar al servicio híbrido
const result = await this.hybridAuthService.register(registerData);
}// hybrid-auth.service.ts
async register(data: RegisterRequest): Promise<any> {
// Usar aws-amplify/auth para el registro
const result = await signUp({
username: data.email,
password: data.password,
options: {
userAttributes: {
email: data.email,
name: data.name,
phone_number: data.phone,
'custom:school': data.school,
'custom:age': data.age.toString(),
'custom:gender': data.gender,
'custom:role': 'student'
}
}
});
return result;
}¿Cómo se envía el código de confirmación?
-
Automáticamente por AWS Cognito:
- Cuando llamas a
signUp(), Cognito automáticamente:- Crea el usuario en estado "UNCONFIRMED"
- Genera un código de 6 dígitos aleatorio
- Envía un email usando AWS SES (Simple Email Service)
- Cuando llamas a
-
Formato del Email:
De: no-reply@verificationemail.com (o tu dominio configurado) Asunto: Your verification code Su código de verificación es: 123456 -
Configuración en AWS Cognito:
User Pool → Message customizations → Verification type: ✅ Email (selected) Email verification message: "Tu código de verificación es {####}" Email verification subject: "Confirma tu cuenta en EdTech"
// register.component.ts
async confirmRegistration(code: string) {
try {
// Llamar al servicio para confirmar
const result = await this.hybridAuthService.confirmRegistration(
this.email,
code
);
if (result.success) {
// Usuario confirmado ✅
this.router.navigate(['/login']);
}
} catch (error) {
// Código inválido o expirado
this.showError(error);
}
}// hybrid-auth.service.ts
async confirmRegistration(email: string, code: string): Promise<any> {
// Usar aws-amplify/auth para confirmar
const result = await confirmSignUp({
username: email,
confirmationCode: code // ← El código de 6 dígitos
});
return {
success: result.isSignUpComplete
};
}// hybrid-auth.service.ts
async resendConfirmationCode(email: string): Promise<any> {
// Usar aws-amplify/auth para reenviar
const result = await resendSignUpCode({
username: email
});
// Cognito volverá a enviar el email automáticamente
return result;
}┌─────────────┐
│ Usuario │
│ Registra │
└──────┬──────┘
│
│ 1. signUp(email, password, attrs)
▼
┌──────────────────┐
│ AuthService │
│ (aws-amplify) │
└──────┬───────────┘
│
│ 2. HTTP POST a Cognito
▼
┌──────────────────────────────────────┐
│ AWS Cognito User Pool │
│ │
│ 1. Crea usuario (estado: UNCONFIRMED)
│ 2. Genera código: "123456" │
│ 3. Llama a AWS SES │
└──────┬───────────────────────────────┘
│
│ 3. AWS SES envía email
▼
┌──────────────────┐
│ Email del User │
│ │
│ Código: 123456 │ ◄─── 📧 Llega al inbox
└──────┬───────────┘
│
│ 4. Usuario lee email
│ 5. Ingresa código en app
▼
┌──────────────────┐
│ Confirm Dialog │
│ │
│ [______] │ ◄─── Usuario escribe "123456"
│ [Confirmar] │
└──────┬───────────┘
│
│ 6. confirmSignUp(email, "123456")
▼
┌──────────────────┐
│ AuthService │
└──────┬───────────┘
│
│ 7. HTTP POST a Cognito
▼
┌──────────────────────────────────────┐
│ AWS Cognito User Pool │
│ │
│ 1. Valida el código │
│ 2. Si es correcto: │
│ - Cambia estado a CONFIRMED ✅ │
│ 3. Si es incorrecto: │
│ - Error: CodeMismatchException │
└──────┬───────────────────────────────┘
│
│ 8. Respuesta
▼
┌──────────────────┐
│ Usuario │
│ Confirmado ✅ │
│ │
│ → Login Page │
└──────────────────┘
Características del código:
- ✅ 6 dígitos numéricos (ej: 123456)
- ✅ Válido por 24 horas (configurable en Cognito)
- ✅ Un solo uso (se invalida después de confirmar)
- ✅ Generado por Cognito (no por tu app)
- ✅ Enviado automáticamente por AWS SES
- ✅ Se puede reenviar con
resendSignUpCode()
Posibles errores:
// Error: Código incorrecto
CodeMismatchException: "Invalid verification code provided"
// Error: Código expirado
ExpiredCodeException: "Invalid code provided, please request a code again"
// Error: Usuario ya confirmado
NotAuthorizedException: "User cannot be confirmed. Current status is CONFIRMED"// login.component.ts
async loginWithHostedUI() {
// Abrir el Cognito Hosted UI (navegador)
await this.oauthService.login();
}// oauth.service.ts
async login(): Promise<void> {
// 1. Generar PKCE (Proof Key for Code Exchange)
const codeVerifier = this.generateRandomString(32);
const codeChallenge = this.sha256(codeVerifier);
// 2. Guardar code_verifier para después
localStorage.setItem('oauth_code_verifier', codeVerifier);
// 3. Construir URL del Hosted UI
const params = new URLSearchParams({
response_type: 'code',
client_id: environment.aws.clientId,
redirect_uri: 'http://localhost:8101/callback',
scope: 'openid email',
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
const authUrl = `https://edtech-auth.auth.us-east-1.amazoncognito.com/login?${params}`;
// 4. Abrir en el navegador del dispositivo
await Browser.open({ url: authUrl });
}┌────────────────────────────────────────┐
│ AWS Cognito Hosted UI │
│ (Página web de AWS) │
├────────────────────────────────────────┤
│ │
│ 📧 Email: [___________________] │
│ 🔒 Password: [___________________] │
│ │
│ [ Iniciar Sesión ] │
│ │
│ ¿Olvidaste tu contraseña? │
│ ¿No tienes cuenta? Regístrate │
│ │
└────────────────────────────────────────┘
↓
(Usuario ingresa email + password)
↓
Cognito valida credenciales
↓
Si es correcto: redirige a callback
// App.ts - Deep Link Listener
App.addListener('appUrlOpen', async (data) => {
// URL recibida: http://localhost:8101/callback?code=abc123xyz...
const url = new URL(data.url);
const code = url.searchParams.get('code'); // ← Authorization Code
if (code) {
// Cerrar el browser
await Browser.close();
// Intercambiar código por tokens
await this.oauthService.exchangeCodeForTokens(code);
}
});// oauth.service.ts
private async exchangeCodeForTokens(code: string): Promise<void> {
// 1. Recuperar el code_verifier guardado
const codeVerifier = localStorage.getItem('oauth_code_verifier');
// 2. Hacer petición al endpoint de tokens
const params = new URLSearchParams({
grant_type: 'authorization_code',
client_id: environment.aws.clientId,
code: code, // ← El código recibido
redirect_uri: 'http://localhost:8101/callback',
code_verifier: codeVerifier // ← PKCE: proof que iniciamos el flow
});
// 3. POST a /oauth2/token
const response = await fetch(
'https://edtech-auth.auth.us-east-1.amazoncognito.com/oauth2/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString()
}
);
// 4. Respuesta con tokens
const tokens = await response.json();
/*
{
"access_token": "eyJraWQiOiJ...",
"id_token": "eyJraWQiOiJ...",
"refresh_token": "eyJjdHkiOiJ...",
"expires_in": 3600,
"token_type": "Bearer"
}
*/
// 5. Guardar tokens en localStorage
localStorage.setItem('oauth_access_token', tokens.access_token);
localStorage.setItem('oauth_id_token', tokens.id_token);
localStorage.setItem('oauth_refresh_token', tokens.refresh_token);
localStorage.setItem('oauth_expires_at', Date.now() + tokens.expires_in * 1000);
// 6. Cargar información del usuario
await this.loadUserInfo();
// 7. Redirigir a la app
this.router.navigate(['/bot']);
}// oauth.service.ts
private async loadUserInfo(): Promise<void> {
const accessToken = localStorage.getItem('oauth_access_token');
// Llamar al endpoint userInfo de Cognito
const response = await fetch(
'https://edtech-auth.auth.us-east-1.amazoncognito.com/oauth2/userInfo',
{
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
const userInfo = await response.json();
/*
{
"sub": "uuid-del-usuario",
"email": "user@example.com",
"email_verified": true,
"name": "Juan Pérez",
"custom:school": "Universidad XYZ",
"custom:age": "20",
"custom:gender": "male",
"custom:role": "student"
}
*/
localStorage.setItem('oauth_user_info', JSON.stringify(userInfo));
}Responsabilidad: Operaciones de registro y gestión de cuentas usando aws-amplify/auth
@Injectable({ providedIn: 'root' })
export class AuthService {
// ✅ Configuración de Amplify
constructor() {
Amplify.configure({
Auth: {
Cognito: {
userPoolId: 'us-east-1_DI5ZL4Ecg',
userPoolClientId: '20rtl03enqpogedn5r5g79pqdm',
loginWith: {
oauth: {
domain: 'edtech-auth.auth.us-east-1.amazoncognito.com',
scopes: ['openid', 'email'],
redirectSignIn: ['http://localhost:8101/callback'],
redirectSignOut: ['http://localhost:8101/signout'],
responseType: 'code'
}
}
}
}
});
}
// ✅ MÉTODOS QUE SÍ USA (con aws-amplify/auth)
register(data: RegisterRequest): Observable<any> {
return from(signUp({
username: data.email,
password: data.password,
options: { userAttributes: {...} }
}));
}
confirmRegistration(email: string, code: string): Observable<any> {
return from(confirmSignUp({
username: email,
confirmationCode: code
}));
}
resendConfirmationCode(email: string): Observable<any> {
return from(resendSignUpCode({ username: email }));
}
forgotPassword(data: ForgotPasswordRequest): Observable<any> {
return from(resetPassword({ username: data.email }));
}
confirmNewPassword(email: string, code: string, newPassword: string): Observable<any> {
return from(confirmResetPassword({
username: email,
confirmationCode: code,
newPassword: newPassword
}));
}
// ❌ NO USA para OAuth login
// El login se hace con OAuthService
}Responsabilidad: Flujo OAuth 2.0 PKCE manual con Hosted UI
@Injectable({ providedIn: 'root' })
export class OAuthService {
// ✅ Login con Hosted UI
async login(): Promise<void> {
const { codeVerifier, codeChallenge } = this.generatePKCE();
localStorage.setItem('oauth_code_verifier', codeVerifier);
const authUrl = this.buildAuthUrl(codeChallenge);
await Browser.open({ url: authUrl });
}
// ✅ Intercambio de código por tokens
private async exchangeCodeForTokens(code: string): Promise<void> {
const codeVerifier = localStorage.getItem('oauth_code_verifier');
const tokens = await this.fetchTokens(code, codeVerifier);
this.saveTokens(tokens);
await this.loadUserInfo();
}
// ✅ Refresh de token expirado
async refreshToken(): Promise<boolean> {
const refreshToken = localStorage.getItem('oauth_refresh_token');
const newTokens = await this.fetchRefreshTokens(refreshToken);
this.saveTokens(newTokens);
return true;
}
// ✅ Verificar autenticación
async isAuthenticated(): Promise<boolean> {
const token = localStorage.getItem('oauth_access_token');
const expiresAt = localStorage.getItem('oauth_expires_at');
if (!token || !expiresAt) return false;
if (Date.now() >= parseInt(expiresAt)) {
return await this.refreshToken();
}
return true;
}
// ✅ Obtener tokens
getAccessToken(): string | null {
return localStorage.getItem('oauth_access_token');
}
getIdToken(): string | null {
return localStorage.getItem('oauth_id_token');
}
// ✅ Obtener usuario
getCurrentUser(): UserInfo | null {
const userInfoStr = localStorage.getItem('oauth_user_info');
return userInfoStr ? JSON.parse(userInfoStr) : null;
}
// ✅ Logout
async logout(): Promise<void> {
// Limpiar localStorage
localStorage.clear();
// Abrir logout URL de Cognito
const logoutUrl = this.buildLogoutUrl();
await Browser.open({ url: logoutUrl });
}
}Responsabilidad: Interacción con el backend (MongoDB) y decodificación de JWT
@Injectable({ providedIn: 'root' })
export class UserService {
// ✅ Enviar datos del primer login a MongoDB
async firstLogin(attributes: UserAttributes): Promise<Observable<FirstLoginResponse>> {
const headers = await this.getAuthHeaders();
return this.http.post<FirstLoginResponse>(
environment.api.firstLogin,
{ attributes },
{ headers }
);
}
// ✅ Verificar si el usuario existe en MongoDB
async checkUserExists(): Promise<CheckUserResponse | null> {
const headers = await this.getAuthHeaders();
return this.http.get<CheckUserResponse>(
environment.api.checkUserExists,
{ headers }
).toPromise();
}
// ✅ Obtener info del usuario desde el ID Token (JWT)
async getUserInfoFromCognito(): Promise<CognitoUserInfo | null> {
const idToken = await this.authService.getIdToken();
if (!idToken) return null;
// Decodificar el JWT
const payload = this.decodeJWT(idToken);
return payload as CognitoUserInfo;
}
// ✅ Verificar si necesita completar first-login
async needsFirstLogin(): Promise<boolean> {
const response = await this.checkUserExists();
return response?.isFirstLogin === true || response?.exists === false;
}
}Responsabilidad: Proteger rutas y manejar first-login
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
// 1. Verificar autenticación OAuth
const isAuthenticated = await this.oauthService.isAuthenticated();
if (!isAuthenticated) {
this.router.navigate(['/login']);
return false;
}
// 2. Verificar si existe en MongoDB
try {
const userExists = await this.userService.checkUserExists();
if (!userExists || userExists.isFirstLogin) {
// Usuario no existe en MongoDB → Redirigir a registro
this.router.navigate(['/register']);
return false;
}
// Usuario existe → Permitir acceso
return true;
} catch (error) {
console.error('Error verificando usuario:', error);
this.router.navigate(['/login']);
return false;
}
}
}┌──────────────────────────────────────────────────────────────────┐
│ FLUJO DE REGISTRO COMPLETO │
└──────────────────────────────────────────────────────────────────┘
👤 Usuario
│
│ 1. Abre /register
▼
┌─────────────────┐
│ RegisterComponent│
│ │
│ Formulario: │
│ - Name │
│ - Email │
│ - Password │
│ - School │
│ - Age │
│ - Gender │
│ - Phone │
└────────┬────────┘
│
│ 2. Click "Registrar"
▼
┌─────────────────────┐
│ HybridAuthService │
│ .register(data) │
└────────┬────────────┘
│
│ 3. signUp(username, password, attributes)
▼
┌─────────────────────────────────────────┐
│ AWS Cognito User Pool │
│ │
│ 1. Valida password policy │
│ 2. Verifica que email no exista │
│ 3. Crea usuario (estado: UNCONFIRMED) │
│ 4. Genera código: "123456" │
└────────┬────────────────────────────────┘
│
│ 4. Llama a AWS SES
▼
┌─────────────────────┐
│ AWS SES │
│ (Email Service) │
└────────┬────────────┘
│
│ 5. Envía email con código
▼
┌─────────────────────┐ ┌─────────────────────┐
│ Email del Usuario │ │ App muestra │
│ │ │ Alert Dialog │
│ Código: 123456 │ ←───→ │ │
└─────────────────────┘ │ [____________] │
│ Ingresa código │
│ [Confirmar] │
└────────┬────────────┘
│
│ 6. Usuario ingresa código
▼
┌─────────────────────┐
│ confirmRegistration │
│ (email, "123456") │
└────────┬────────────┘
│
│ 7. confirmSignUp()
▼
┌─────────────────────────────┐
│ AWS Cognito User Pool │
│ │
│ 1. Valida código │
│ 2. Si correcto: │
│ - Estado → CONFIRMED ✅ │
│ 3. Si incorrecto: │
│ - Error ❌ │
└────────┬────────────────────┘
│
│ 8. Respuesta
▼
┌─────────────────────┐
│ Usuario │
│ Confirmado ✅ │
│ │
│ Redirige a /login │
└─────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ FLUJO DE LOGIN CON OAUTH PKCE │
└──────────────────────────────────────────────────────────────────┘
👤 Usuario
│
│ 1. Abre /login
▼
┌─────────────────┐
│ LoginComponent │
│ │
│ [Login OAuth] │ ← Click aquí
└────────┬────────┘
│
│ 2. oauthService.login()
▼
┌──────────────────────────────────────┐
│ OAuthService │
│ │
│ 1. Genera PKCE: │
│ - code_verifier (random) │
│ - code_challenge = SHA256(^) │
│ │
│ 2. Guarda en localStorage: │
│ - oauth_code_verifier │
│ │
│ 3. Construye URL: │
│ /oauth2/authorize? │
│ response_type=code │
│ &client_id=xxx │
│ &redirect_uri=callback │
│ &code_challenge=yyy │
│ &code_challenge_method=S256 │
└────────┬─────────────────────────────┘
│
│ 4. Browser.open(authUrl)
▼
┌────────────────────────────────────┐
│ 📱 Capacitor Browser │
│ (In-App Browser) │
│ │
│ ┌─────────────────────────────┐ │
│ │ AWS Cognito Hosted UI │ │
│ │ (edtech-auth.auth.xxx) │ │
│ │ │ │
│ │ Email: [____________] │ │
│ │ Password: [____________] │ │
│ │ │ │
│ │ [ Iniciar Sesión ] │ │
│ └─────────────────────────────┘ │
└────────┬───────────────────────────┘
│
│ 5. Usuario ingresa credenciales
▼
┌─────────────────────────────────────┐
│ AWS Cognito User Pool │
│ │
│ 1. Valida email + password │
│ 2. Verifica que esté CONFIRMED │
│ 3. Genera authorization_code │
│ 4. Redirige a: │
│ callback?code=abc123xyz... │
└────────┬────────────────────────────┘
│
│ 6. Redirect con code
▼
┌────────────────────────────────────┐
│ App Deep Link Listener │
│ (App.addListener) │
│ │
│ URL: callback?code=abc123xyz │
└────────┬───────────────────────────┘
│
│ 7. Browser.close()
│ 8. exchangeCodeForTokens(code)
▼
┌──────────────────────────────────────────────────┐
│ OAuthService │
│ │
│ 1. Lee code_verifier de localStorage │
│ 2. POST a /oauth2/token con: │
│ - grant_type=authorization_code │
│ - client_id=xxx │
│ - code=abc123xyz │
│ - redirect_uri=callback │
│ - code_verifier=zzz ← PKCE proof │
└────────┬─────────────────────────────────────────┘
│
│ 9. Petición a Cognito
▼
┌─────────────────────────────────────┐
│ AWS Cognito Token Endpoint │
│ │
│ 1. Valida code │
│ 2. Valida code_verifier con │
│ code_challenge (PKCE) │
│ 3. Genera tokens: │
│ - access_token (JWT) │
│ - id_token (JWT) │
│ - refresh_token │
└────────┬────────────────────────────┘
│
│ 10. Respuesta con tokens
▼
┌──────────────────────────────────────┐
│ OAuthService │
│ │
│ 1. Guarda en localStorage: │
│ - oauth_access_token │
│ - oauth_id_token │
│ - oauth_refresh_token │
│ - oauth_expires_at │
│ │
│ 2. loadUserInfo() │
└────────┬─────────────────────────────┘
│
│ 11. GET /oauth2/userInfo
▼
┌─────────────────────────────────────┐
│ AWS Cognito UserInfo Endpoint │
│ │
│ Retorna: │
│ { │
│ sub: "uuid", │
│ email: "user@example.com", │
│ name: "Juan", │
│ custom:school: "UNAM", │
│ custom:age: "20", │
│ ... │
│ } │
└────────┬────────────────────────────┘
│
│ 12. Guarda user_info
▼
┌──────────────────────────────────────┐
│ localStorage │
│ │
│ oauth_access_token: "eyJ..." │
│ oauth_id_token: "eyJ..." │
│ oauth_refresh_token: "eyJ..." │
│ oauth_expires_at: 1730000000000 │
│ oauth_user_info: "{...}" │
└────────┬─────────────────────────────┘
│
│ 13. router.navigate(['/bot'])
▼
┌─────────────────────┐
│ AuthGuard │
│ │
│ 1. Verifica token │
│ 2. Verifica MongoDB│
└────────┬────────────┘
│
│ 14. checkUserExists()
▼
┌──────────────────────────────────────┐
│ UserService │
│ │
│ GET /users/check-exists │
│ Header: Authorization: Bearer xxx │
└────────┬─────────────────────────────┘
│
│ 15. Consulta a Lambda + MongoDB
▼
┌──────────────────────────────────────┐
│ AWS Lambda + MongoDB │
│ │
│ 1. Decodifica JWT │
│ 2. Extrae email/sub │
│ 3. Busca en MongoDB │
│ 4. Retorna: │
│ - exists: true/false │
│ - isFirstLogin: true/false │
│ - user: {...} │
└────────┬─────────────────────────────┘
│
│ 16. Respuesta
▼
┌──────────────────────────────────────┐
│ AuthGuard Decision │
│ │
│ Si exists=true → /bot ✅ │
│ Si exists=false → /register ⚠️ │
└──────────────────────────────────────┘
export const environment = {
production: false,
aws: {
region: 'us-east-1',
userPoolId: 'us-east-1_DI5ZL4Ecg',
clientId: '20rtl03enqpogedn5r5g79pqdm',
domain: 'https://edtech-auth.auth.us-east-1.amazoncognito.com',
issuer: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_DI5ZL4Ecg',
jwks_uri: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_DI5ZL4Ecg/.well-known/jwks.json',
redirectUri: 'http://localhost:8101/callback',
signOutUri: 'http://localhost:8101/signout',
scopes: ['openid', 'email'],
responseType: 'code'
}
};User Pool ID: us-east-1_DI5ZL4Ecg
Region: us-east-1
Name: edtech-user-pool
App Client ID: 20rtl03enqpogedn5r5g79pqdm
App Client Name: edtech-app-client
Authentication Flows:
✅ ALLOW_USER_PASSWORD_AUTH
✅ ALLOW_REFRESH_TOKEN_AUTH
✅ ALLOW_AUTHORIZATION_CODE_GRANT (OAuth)
OAuth 2.0 Settings:
✅ Authorization code grant
✅ Implicit grant (NO - más seguro con PKCE)
Allowed OAuth Flows:
✅ Authorization code grant
Allowed OAuth Scopes:
✅ openid
✅ email
✅ profile
Callback URLs:
http://localhost:8100/callback
http://localhost:8101/callback
edtech://callback (para móvil)
Sign out URLs:
http://localhost:8100/signout
http://localhost:8101/signout
edtech://signout
Domain: edtech-auth.auth.us-east-1.amazoncognito.com
Hosted UI URLs:
- Login: https://edtech-auth.auth.us-east-1.amazoncognito.com/login
- Logout: https://edtech-auth.auth.us-east-1.amazoncognito.com/logout
- OAuth2 Authorize: .../oauth2/authorize
- OAuth2 Token: .../oauth2/token
- OAuth2 UserInfo: .../oauth2/userInfo
Atributos Estándar:
✅ email (required, mutable, verifiable)
✅ name (required, mutable)
✅ phone_number (required, mutable)
Atributos Personalizados:
✅ custom:school (String, mutable)
✅ custom:age (Number, mutable)
✅ custom:gender (String, mutable)
✅ custom:role (String, mutable)
Minimum length: 8
Require lowercase: ✅
Require uppercase: ✅
Require numbers: ✅
Require symbols: ✅
Expiration: Never
Verification method: Email
Verification message:
"Tu código de verificación es {####}"
Verification subject:
"Confirma tu cuenta en EdTech"
From email:
no-reply@verificationemail.com (AWS SES)
Pre sign-up: check_user_lambda (verifica si ya existe en MongoDB)
Post confirmation: None
Pre authentication: None
Post authentication: None
-
"Sabor" de Cognito:
- ✅ NO es AWS Amplify completo
- ✅ Es
aws-amplify/authv6 + OAuth manual - ✅ PKCE implementado manualmente
- ✅ Hosted UI de Cognito
-
Flujo de Registro:
- ✅ Usuario → signUp() → Cognito crea usuario (UNCONFIRMED)
- ✅ Cognito → AWS SES → Envía email con código de 6 dígitos
- ✅ Usuario → Ingresa código → confirmSignUp()
- ✅ Cognito → Valida código → Estado = CONFIRMED ✅
-
Flujo de Login:
- ✅ Usuario → Click "Login" → Abre Hosted UI
- ✅ Hosted UI → Usuario ingresa credenciales
- ✅ Cognito → Valida → Redirige con authorization_code
- ✅ App → Intercambia code por tokens (PKCE)
- ✅ Tokens guardados en localStorage
- ✅ AuthGuard verifica si existe en MongoDB
-
Código de Confirmación (PIN):
- ✅ Generado automáticamente por AWS Cognito
- ✅ Enviado automáticamente por AWS SES
- ✅ 6 dígitos numéricos
- ✅ Válido 24 horas
- ✅ Un solo uso
- ✅ Se puede reenviar con
resendSignUpCode()
-
Tokens:
- ✅ access_token: Para llamadas al backend
- ✅ id_token: Contiene info del usuario (JWT)
- ✅ refresh_token: Para renovar tokens expirados
- ✅ Guardados en localStorage
- ✅ Validez: 1 hora (configurable)