Inicio de sesión (Sign In) con AWS Cognito en iOS

En los artículos anteriores implementamos el registro de usuarios y la confirmación de cuenta. Ahora tenemos usuarios registrados y confirmados en AWS Cognito, pero aún no pueden acceder a la aplicación. En este artículo vamos a implementar el inicio de sesión para que los usuarios puedan autenticarse y obtener sus tokens de acceso.

¿Qué vamos a construir?

Una pantalla de inicio de sesión que será la pantalla principal de la aplicación, donde el usuario podrá: – Iniciar sesión con email y contraseña – Navegar a la pantalla de registro si no tiene cuenta – Obtener tokens de autenticación de AWS Cognito (Access Token, ID Token, Refresh Token) – Manejar errores comunes (credenciales incorrectas, usuario no confirmado, etc.)

Cambio importante en el flujo de navegación

Hasta ahora nuestra app iniciaba directamente en la pantalla de registro (RegisterView). Esto tiene sentido para aprender, pero en una aplicación real:

  • La pantalla principal es el inicio de sesión (SignInView)
  • Desde ahí el usuario puede ir a registrarse si no tiene cuenta
  • Después de registrarse y confirmar, el usuario vuelve al login para iniciar sesión

El nuevo flujo será:

App inicia → SignInView (pantalla principal)
              ↓
              ├─→ «Iniciar Sesión» → Pantalla de bienvenida
              │
              └─→ «¿No tienes cuenta? Regístrate» → RegisterView
                                                      ↓
                                                   ConfirmCodeView
                                                      ↓
                                                   Vuelve a SignInView

Requisitos previos

  • Haber completado los artículos anteriores (Registro y Confirmación)
  • Tener al menos un usuario CONFIRMED en AWS Cognito
  • Conocer las credenciales (email y contraseña) de un usuario confirmado

Paso 1: Creando la pantalla de inicio de sesión

Vamos a crear SignInView.swift que será la nueva pantalla principal de nuestra app.

1.1 Crear el archivo

En Xcode: 1. Click derecho en la carpeta CognitoAuthDemo 2. New File → SwiftUI View 3. Nombre: SignInView

1.2 Código de SignInView

import SwiftUI

struct SignInView: View {
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var isLoading: Bool = false
    @State private var message: String = ""
    @State private var showAlert: Bool = false
    @State private var navigateToRegister: Bool = false

    var body: some View {
        VStack(spacing: 20) {
            // Logo o título
            Text("Bienvenido")
                .font(.largeTitle)
                .fontWeight(.bold)
                .padding(.bottom, 10)

            Text("Inicia sesión con tu cuenta")
                .font(.subheadline)
                .foregroundColor(.gray)
                .padding(.bottom, 30)

            // Campo de Email
            TextField("Email", text: $email)
                .textInputAutocapitalization(.never)
                .keyboardType(.emailAddress)
                .padding()
                .background(Color(.systemGray6))
                .cornerRadius(10)
                .disabled(isLoading)

            // Campo de Contraseña
            SecureField("Contraseña", text: $password)
                .padding()
                .background(Color(.systemGray6))
                .cornerRadius(10)
                .disabled(isLoading)

            // Mensaje de estado
            if !message.isEmpty {
                Text(message)
                    .font(.caption)
                    .foregroundColor(.gray)
                    .multilineTextAlignment(.center)
                    .padding(.horizontal)
            }

            // Botón de Iniciar Sesión
            Button(action: {
                signIn()
            }) {
                if isLoading {
                    ProgressView()
                        .progressViewStyle(CircularProgressViewStyle(tint: .white))
                        .frame(maxWidth: .infinity)
                        .padding()
                } else {
                    Text("Iniciar Sesión")
                        .fontWeight(.semibold)
                        .foregroundColor(.white)
                        .frame(maxWidth: .infinity)
                        .padding()
                }
            }
            .background(isLoading ? Color.gray : Color.blue)
            .cornerRadius(10)
            .disabled(isLoading)
            .padding(.top, 20)

            // Enlace a Registro
            HStack {
                Text("¿No tienes cuenta?")
                    .foregroundColor(.gray)

                Button(action: {
                    navigateToRegister = true
                }) {
                    Text("Regístrate")
                        .fontWeight(.semibold)
                        .foregroundColor(.blue)
                }
            }
            .padding(.top, 10)

            Spacer()
        }
        .padding(.horizontal, 30)
        .padding(.top, 80)
        .alert("Inicio de Sesión", isPresented: $showAlert) {
            Button("OK", role: .cancel) { }
        } message: {
            Text(message)
        }
        .navigationDestination(isPresented: $navigateToRegister) {
            RegisterView()
        }
    }

    // MARK: - Función de Inicio de Sesión
    private func signIn() {
        // Validaciones básicas
        guard !email.isEmpty, !password.isEmpty else {
            message = "Por favor completa todos los campos"
            showAlert = true
            return
        }

        // Aquí irá la lógica de inicio de sesión con AWS Cognito
        isLoading = true
        message = "Iniciando sesión..."

        // Por ahora, solo simulamos
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            isLoading = false
            message = "Inicio de sesión exitoso"
            showAlert = true
        }
    }
}

1.3 ¿Qué hace este código?

Estados locales (@State): – email y password: Almacenan las credenciales del usuario – isLoading: Controla el indicador de carga – message: Mensajes para el usuario – showAlert: Controla cuándo mostrar alertas – navigateToRegister: Controla la navegación a la pantalla de registro

Elementos de la interfaz: 1. Título: “Bienvenido” con subtítulo 2. Campo de Email: Con teclado de email y sin capitalización 3. Campo de Contraseña: SecureField para ocultar el texto 4. Botón “Iniciar Sesión”: Con spinner cuando está procesando 5. Enlace “¿No tienes cuenta? Regístrate”: Navega a RegisterView

Diferencias con RegisterView: – Solo pide email y password (no confirmación de password) – Tiene enlace para ir a registro en lugar de al revés – Función signIn() en lugar de registerUser() – Por ahora simula el inicio de sesión, luego lo conectaremos con AWS Cognito

Navegación: – .navigationDestination permite navegar a RegisterView cuando el usuario hace click en “Regístrate”

Paso 2: Actualizando la navegación de la app

Ahora que tenemos SignInView, necesitamos cambiar el flujo de navegación para que la app inicie en el login en lugar del registro.

2.1 Actualizar ContentView

Cambiamos ContentView.swift para que inicie en SignInView:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            SignInView()  // Cambiado de RegisterView() a SignInView()
        }
    }
}

¿Qué cambió? – Antes la app iniciaba en RegisterView() – Ahora inicia en SignInView() – Esto hace más sentido: primero login, luego opción de registro

2.2 Actualizar RegisterView para navegación bidireccional

Para que el usuario pueda regresar al login desde la pantalla de registro, agregamos un enlace “¿Ya tienes cuenta? Inicia sesión” en RegisterView.swift.

Primero, agregamos el @Environment para poder volver atrás:

struct RegisterView: View {
    @Environment(\.dismiss) private var dismiss  // Nuevo

    @State private var email: String = ""
    @State private var password: String = ""
    // ... resto de los estados

Luego, después del botón de “Registrarse”, agregamos el enlace:

.padding(.top, 20)

// Enlace para volver al Login (Nuevo)
HStack {
    Text("¿Ya tienes cuenta?")
        .foregroundColor(.gray)

    Button(action: {
        dismiss()
    }) {
        Text("Inicia sesión")
            .fontWeight(.semibold)
            .foregroundColor(.blue)
    }
}
.padding(.top, 10)

Spacer()

¿Qué hace esto? – @Environment(\.dismiss): Permite volver atrás en el stack de navegación – dismiss(): Cierra la vista actual y regresa a SignInView – Similar al enlace que tiene SignInView pero al revés

2.3 Flujo de navegación completo

Ahora el flujo de navegación es:

App inicia
    ↓
ContentView (NavigationStack)
    ↓
SignInView (pantalla principal)
    ↓
    ├─→ Click «Regístrate» → RegisterView
    │                            ↓
    │                            ├─→ Click «Inicia sesión» → dismiss() → vuelve a SignInView
    │                            ↓
    │                         Registro exitoso → ConfirmCodeView
    │                                               ↓
    │                                            Confirmación exitosa
    │
    └─→ Click «Iniciar Sesión» → (próximo paso: pantalla de inicio)

Importante: – SignInView navega hacia adelante a RegisterView – RegisterView navega hacia atrás a SignInView usando dismiss() – Esto mantiene un stack de navegación limpio y predecible

Paso 3: Configurar el flujo de autenticación en AWS Cognito

Antes de implementar el código, necesitamos configurar el App Client en AWS Cognito para permitir el flujo de autenticación que vamos a usar. Sin este paso, el código dará error.

3.1 ¿Por qué necesitamos esta configuración?

AWS Cognito tiene varios flujos de autenticación (authentication flows) para diferentes casos de uso:

  • USER_PASSWORD_AUTH: El usuario envía email y contraseña directamente a Cognito
  • USER_SRP_AUTH: Usa SRP (Secure Remote Password) – más seguro, la contraseña nunca sale del dispositivo
  • CUSTOM_AUTH: Autenticación personalizada (ej: con SMS, biometría, etc.)
  • REFRESH_TOKEN_AUTH: Para obtener nuevos tokens usando el refresh token

Por seguridad, estos flujos están deshabilitados por defecto. Necesitamos habilitar explícitamente los que vamos a usar.

3.2 Habilitar el flujo USER_PASSWORD_AUTH

Paso a paso en AWS Console:

  1. Ir a Cognito:
    • AWS Console → Cognito
    • Click en tu User Pool (ej: us-east-1_NmaGmKWMF)
  2. Ir a App Integration:
    • Click en la pestaña “App integration”
    • Scroll hacia abajo hasta “App clients and analytics”
    • Deberías ver tu App Client listado
  3. Editar el App Client:
    • Click en el nombre del App Client (el que tiene tu Client ID)
    • Click en botón “Edit” (arriba a la derecha)
  4. Habilitar flujos de autenticación:
    • En la sección “Authentication flows”, marca estas opciones:
      • ALLOW_USER_PASSWORD_AUTH
        • Permite autenticación directa con email/password
        • Es el que vamos a usar en este artículo
      • ALLOW_REFRESH_TOKEN_AUTH
        • Permite refrescar tokens cuando expiren
        • Siempre recomendado habilitarlo
      • Opcional (no necesario para este artículo):
        • ALLOW_USER_SRP_AUTH (más seguro, pero más complejo)
        • ALLOW_CUSTOM_AUTH (para flujos personalizados)
      • Guardar cambios:
        • Click en “Save changes” al final de la página

3.3 Entender la configuración de tokens

En la misma pantalla de edición del App Client, verás la sección “Token expiration” (Vencimiento de tokens). Aquí se configuran los tiempos de vida de cada token.

Valores por defecto:

TokenTiempo de vida por defecto¿Qué significa?
Authentication flow session duration3 minutosTiempo máximo para completar un flujo de autenticación (ej: MFA)
Refresh token expiration30 díasCuánto tiempo el refresh token es válido
Access token expiration60 minutosCuánto tiempo el access token es válido
ID token expiration60 minutosCuánto tiempo el ID token es válido

¿Qué es cada token?

1. Authentication flow session duration (Duración de sesión del flujo):No es un token, es un tiempo límite – Aplica cuando hay pasos adicionales como MFA (autenticación multifactor) – Ejemplo: Si el usuario tarda más de 3 minutos en ingresar el código MFA, la sesión expira – Para este artículo: No aplica porque no usamos MFA – Valor recomendado: 3-15 minutos

2. Refresh Token (Token de actualización):Propósito: Obtener nuevos Access e ID tokens sin pedir credenciales de nuevo – Vida útil larga: Días o semanas – Debe guardarse de forma muy segura (Keychain en iOS) – Si se compromete, un atacante puede generar nuevos tokens – Valor recomendado: – Apps móviles: 30-90 días – Apps web: 7-30 días – Valor por defecto: 30 días

3. Access Token (Token de acceso):Propósito: Autorizar acceso a recursos protegidos (APIs, servicios AWS) – Se envía en cada petición HTTP: Authorization: Bearer <access-token> – Contiene scopes (permisos) del usuario – No contiene información personal del usuario – Vida útil corta: Minutos u horas – Valor recomendado: – Alta seguridad: 5-15 minutos – Balance: 60 minutos – Conveniencia: 120 minutos – Valor por defecto: 60 minutos

4. ID Token (Token de identidad):Propósito: Contiene información del usuario (claims) – Incluye: email, nombre, user_sub, etc. – Formato: JWT (JSON Web Token) – Usado por la app para mostrar info del usuario – No debe enviarse a APIs, solo el Access Token – Vida útil: Usualmente igual que Access Token – Valor recomendado: 60 minutos – Valor por defecto: 60 minutos

¿Deberías cambiar los valores por defecto?

Para este artículo: NO, los valores por defecto están bien.

Para producción, considera:

EscenarioRefresh TokenAccess TokenID Token
App bancaria (alta seguridad)7 días5 minutos5 minutos
App corporativa30 días15 minutos15 minutos
App de redes sociales90 días60 minutos60 minutos
App de juegos180 días120 minutos120 minutos

Reglas generales:Refresh Token largo = Usuario no tiene que hacer login frecuentemente – Access Token corto = Más seguro (si se roba, expira rápido) – Balance entre seguridad y experiencia de usuario

3.4 Verificar la configuración

Después de guardar, verifica que:

  1. Authentication flows:
    1. ✅ ALLOW_USER_PASSWORD_AUTH está marcado
    1. ✅ ALLOW_REFRESH_TOKEN_AUTH está marcado
  2. Token expiration:
    1. Refresh token: 30 días (o el valor que elegiste)
    1. Access token: 60 minutos
    1. ID token: 60 minutos

Importante: Si cambias estos valores después de que usuarios ya iniciaron sesión, los tokens existentes mantienen su tiempo de expiración original. Los nuevos valores solo aplican a tokens generados después del cambio.

Paso 4: Implementando el método signIn() en CognitoAuthService

Ahora que configuramos el App Client, vamos a implementar la lógica real de inicio de sesión. Abrimos CognitoAuthService.swift y agregamos el método signIn() después de resendConfirmationCode().

4.1 Crear el modelo de tokens

Primero, necesitamos un modelo para representar los tokens que AWS Cognito nos devolverá. Al final del archivo (fuera de la clase), agregamos:

// MARK: - Modelo de Tokens
struct AuthTokens {
    let accessToken: String
    let idToken: String
    let refreshToken: String
}

¿Qué son estos tokens?Access Token: Token para acceder a recursos protegidos (APIs) – ID Token: Contiene información del usuario (claims) – Refresh Token: Para obtener nuevos tokens cuando expiren

4.2 Implementar el método signIn()

Dentro de la clase CognitoAuthService, después del método resendConfirmationCode(), agregamos:

// MARK: - Inicio de Sesión
func signIn(email: String, password: String) async throws -> AuthTokens {
    guard let client = cognitoClient else {
        throw NSError(domain: "CognitoAuthService", code: -1,
                     userInfo: [NSLocalizedDescriptionKey: "Cliente de Cognito no inicializado"])
    }

    // Crear los parámetros de autenticación
    let authParameters = [
        "USERNAME": email,
        "PASSWORD": password
    ]

    // Crear la solicitud de inicio de sesión
    let initiateAuthInput = InitiateAuthInput(
        authFlow: .userPasswordAuth,
        authParameters: authParameters,
        clientId: clientId
    )

    do {
        let response = try await client.initiateAuth(input: initiateAuthInput)

        print("✅ Inicio de sesión exitoso")

        // Extraer los tokens de la respuesta
        guard let authResult = response.authenticationResult else {
            throw NSError(domain: "CognitoAuthService", code: 4001,
                         userInfo: [NSLocalizedDescriptionKey: "No se pudieron obtener los tokens"])
        }

        let tokens = AuthTokens(
            accessToken: authResult.accessToken ?? "",
            idToken: authResult.idToken ?? "",
            refreshToken: authResult.refreshToken ?? ""
        )

        print("Access Token: \(tokens.accessToken.prefix(20))...")
        print("ID Token: \(tokens.idToken.prefix(20))...")

        return tokens

    } catch {
        // Manejar errores de Cognito
        print("❌ Error al iniciar sesión: \(error)")

        let errorMessage = error.localizedDescription
        if errorMessage.contains("NotAuthorizedException") {
            throw NSError(domain: "CognitoAuthService", code: 4002,
                        userInfo: [NSLocalizedDescriptionKey: "Email o contraseña incorrectos"])
        } else if errorMessage.contains("UserNotConfirmedException") {
            throw NSError(domain: "CognitoAuthService", code: 4003,
                        userInfo: [NSLocalizedDescriptionKey: "Usuario no confirmado. Revisa tu email"])
        } else if errorMessage.contains("UserNotFoundException") {
            throw NSError(domain: "CognitoAuthService", code: 4004,
                        userInfo: [NSLocalizedDescriptionKey: "Usuario no existe"])
        } else {
            throw NSError(domain: "CognitoAuthService", code: 4000,
                        userInfo: [NSLocalizedDescriptionKey: "Error al iniciar sesión: \(errorMessage)"])
        }
    }
}

4.3 ¿Qué hace este método?

Flujo de autenticación:

  1. Parámetros: Recibe email y password del usuario
  2. InitiateAuth: Usa el flujo USER_PASSWORD_AUTH de AWS Cognito
  3. Respuesta exitosa: Extrae los tres tokens de authenticationResult
  4. Retorno: Devuelve un objeto AuthTokens con los tokens

Manejo de errores específicos:

  • NotAuthorizedException: Email o contraseña incorrectos
  • UserNotConfirmedException: El usuario no confirmó su email
  • UserNotFoundException: El usuario no existe en Cognito
  • Otros errores: Error genérico

Importante: – Este es el flujo más directo de autenticación con Cognito – En producción deberías usar HTTPS y considerar OAuth/OIDC flows más seguros – Los tokens son JWT (JSON Web Tokens) que pueden ser validados y decodificados

Paso 5: Conectando SignInView con AWS Cognito

Ahora conectamos la vista con el servicio real para hacer login de verdad.

5.1 Agregar estados para los tokens

En SignInView.swift, agregamos dos nuevos estados:

struct SignInView: View {
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var isLoading: Bool = false
    @State private var message: String = ""
    @State private var showAlert: Bool = false
    @State private var navigateToRegister: Bool = false
    @State private var authTokens: AuthTokens? = nil  // Nuevo

    private let authService = CognitoAuthService()  // Nuevo

¿Qué agregamos? – authTokens: Almacena los tokens recibidos de AWS Cognito (nil si no hay sesión) – authService: Instancia del servicio de autenticación

5.2 Agregar UI para mostrar tokens

Después del mensaje de estado y antes del botón de “Iniciar Sesión”, agregamos:

// Mostrar tokens si existen
if let tokens = authTokens {
    VStack(alignment: .leading, spacing: 10) {
        Text("✅ Tokens recibidos:")
            .font(.headline)
            .foregroundColor(.green)

        VStack(alignment: .leading, spacing: 5) {
            Text("Access Token:")
                .font(.caption)
                .foregroundColor(.gray)
            Text(tokens.accessToken.prefix(60) + "...")
                .font(.system(size: 10, design: .monospaced))
                .foregroundColor(.blue)

            Text("ID Token:")
                .font(.caption)
                .foregroundColor(.gray)
                .padding(.top, 5)
            Text(tokens.idToken.prefix(60) + "...")
                .font(.system(size: 10, design: .monospaced))
                .foregroundColor(.blue)

            Text("Refresh Token:")
                .font(.caption)
                .foregroundColor(.gray)
                .padding(.top, 5)
            Text(tokens.refreshToken.prefix(60) + "...")
                .font(.system(size: 10, design: .monospaced))
                .foregroundColor(.blue)
        }
    }
    .padding()
    .background(Color(.systemGray6))
    .cornerRadius(10)
    .padding(.top, 10)
}

¿Qué hace esto? – Solo se muestra cuando authTokens tiene valor (después de login exitoso) – Muestra los primeros 60 caracteres de cada token – Usa fuente monoespaciada para que parezca código – Visual y educativo: el usuario puede ver que recibió tokens reales

5.3 Actualizar la función signIn()

Reemplazamos la simulación con la llamada real a AWS Cognito:

// MARK: - Función de Inicio de Sesión
private func signIn() {
    // Validaciones básicas
    guard !email.isEmpty, !password.isEmpty else {
        message = "Por favor completa todos los campos"
        showAlert = true
        return
    }

    // Iniciar sesión con AWS Cognito
    isLoading = true
    message = "Iniciando sesión..."
    authTokens = nil  // Limpiar tokens anteriores

    Task {
        do {
            let tokens = try await authService.signIn(email: email, password: password)

            await MainActor.run {
                isLoading = false
                authTokens = tokens
                message = "✅ Inicio de sesión exitoso"
            }
        } catch {
            await MainActor.run {
                isLoading = false
                authTokens = nil
                message = error.localizedDescription
                showAlert = true
            }
        }
    }
}

¿Qué cambió? – Ya no es una simulación con DispatchQueue – Llama al método real authService.signIn() – Si es exitoso, guarda los tokens en authTokens y muestra mensaje de éxito – Si hay error, limpia los tokens y muestra el error – Usa Task y await para manejar la llamada asíncrona

Paso 6: Probando el flujo completo

Ahora vamos a probar todo el flujo de autenticación con un usuario real.

6.1 Preparar datos de prueba

Necesitas un usuario CONFIRMED en tu User Pool de AWS Cognito. Si no tienes uno:

  1. Ejecuta la app en el simulador
  2. Click en “Regístrate”
  3. Registra un usuario con email y contraseña válidos
  4. Confirma el código que llega a tu email
  5. Regresa a la pantalla de inicio de sesión

6.2 Probar inicio de sesión exitoso

En el simulador:

  1. Pantalla inicial: SignInView con campos de email y password
  2. Ingresar credenciales:
    1. Email: El email de tu usuario confirmado
    1. Contraseña: La contraseña que usaste al registrarte
  3. Click “Iniciar Sesión”
  4. Resultado esperado:
    1. Spinner de carga mientras se conecta con AWS
    1. Mensaje: “✅ Inicio de sesión exitoso”
    1. Se muestran los tokens en pantalla:
      1. Access Token: eyJraWQiOiJ… (primeros 60 caracteres)
      1. ID Token: eyJraWQiOiJ… (primeros 60 caracteres)
      1. Refresh Token: eyJjdHkiOi… (primeros 60 caracteres)

6.3 Probar errores comunes

Contraseña incorrecta: – Ingresa email correcto pero contraseña incorrecta – Mensaje esperado: “Email o contraseña incorrectos”

Usuario no existe: – Ingresa un email que no está registrado – Mensaje esperado: “Usuario no existe”

Usuario no confirmado: – Si intentas con un usuario UNCONFIRMED – Mensaje esperado: “Usuario no confirmado. Revisa tu email”

6.4 Verificar en consola de Xcode

En la consola de Xcode deberías ver:

✅ Inicio de sesión exitoso
Access Token: eyJraWQiOiJyRzBVd…
ID Token: eyJraWQiOiJyRzBVd…

Estos logs confirman que: – La conexión con AWS Cognito fue exitosa – Recibiste tokens válidos – Los tokens son JWT (empiezan con eyJ)

6.5 ¿Qué logramos?

Autenticación funcional: Los usuarios pueden iniciar sesión con AWS Cognito ✅ Tokens visibles: Podemos ver los tokens que AWS nos devuelve ✅ Manejo de errores: Mensajes claros para cada tipo de error ✅ Flujo completo: Desde registro → confirmación → inicio de sesión

Nota importante: – Los tokens se están mostrando en pantalla solo con fines educativos – En producción, NUNCA muestres los tokens en la UI – Los tokens deberían guardarse de forma segura (Keychain) y usarse en las cabeceras de las peticiones HTTP

Entendiendo los tokens que recibiste

Después de iniciar sesión exitosamente, AWS Cognito te devuelve 3 tokens. Vamos a entender para qué sirve cada uno.

Access Token (Token de Acceso)

¿Qué es? – Es una credencial temporal que autoriza a tu app a acceder a recursos protegidos – Es un JWT (JSON Web Token) – puedes decodificarlo en jwt.io – Expira después de 60 minutos (por defecto)

¿Para qué sirve? – Lo envías en cada petición HTTP a tu backend o API – Se incluye en el header: Authorization: Bearer <access-token> – El servidor lo valida para verificar que tienes permisos

¿Qué contiene?Scopes: Permisos del usuario (ej: aws.cognito.signin.user.admin) – Username: Identificador del usuario – Token expiration: Timestamp de cuándo expira – NO contiene información personal del usuario (no tiene email, nombre, etc.)

Ejemplo de uso en código:

// Enviar en peticiones HTTP
let headers = [
    "Authorization": "Bearer \(accessToken)",
    "Content-Type": "application/json"
]

// URLRequest
var request = URLRequest(url: url)
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

ID Token (Token de Identidad)

¿Qué es? – Contiene información del usuario (claims) – También es un JWT que puedes decodificar – Expira después de 60 minutos (por defecto)

¿Para qué sirve? – Para obtener información del usuario en tu app – Mostrar nombre, email, foto de perfil en la UI – NO se envía a APIs – solo se usa dentro de la app

¿Qué contiene?

Si decodificas el ID Token en jwt.io, verás algo como:

{
  "sub": "1234-5678-90ab-cdef",        // User ID único e inmutable
  "email": "jose@example.com",         // Email del usuario
  "email_verified": true,              // ¿Email confirmado?
  "cognito:username": "jose",          // Username
  "auth_time": 1234567890,             // Cuándo se autenticó
  "exp": 1234567890,                   // Cuándo expira el token
  "iat": 1234567890                    // Cuándo se emitió el token
}

Ejemplo de uso:

// En una app real, decodificarías el ID Token para extraer el email
// y mostrarlo en la pantalla de perfil del usuario
Text("Bienvenido, \(userEmail)")

Refresh Token (Token de Actualización)

¿Qué es? – Un token de larga duración (30 días por defecto) – Sirve para obtener nuevos Access e ID tokens sin pedir credenciales de nuevo

¿Para qué sirve?

Imagina este escenario:

1. Usuario inicia sesión a las 9:00 AM

2. Recibe Access Token válido por 60 minutos

3. A las 10:00 AM → Access Token expira

4. Sin Refresh Token: Usuario tendría que hacer login otra vez ❌

5. Con Refresh Token: La app solicita nuevos tokens automáticamente ✅

¿Cómo funciona el flujo?

1. Login exitoso
   ↓
   Recibes: Access Token + ID Token + Refresh Token

2. Usas Access Token en peticiones a la API
   ↓
   Funciona bien por 60 minutos

3. Access Token expira
   ↓
   App detecta error 401 (Unauthorized)

4. App usa Refresh Token
   ↓
   Llama a Cognito: «Dame nuevos tokens»

5. Cognito valida el Refresh Token
   ↓
   Devuelve nuevos Access Token + ID Token

6. App continúa funcionando
   Usuario NO necesita hacer login otra vez

Importante sobre seguridad: – El Refresh Token es el más sensible de los 3 – Debe guardarse de forma muy segura (Keychain en iOS) – Si alguien lo roba, puede generar tokens indefinidamente (hasta que expire) – Cuando el usuario hace “Cerrar sesión”, DEBES eliminar el Refresh Token – Nunca lo envíes por HTTP sin encriptar

Resumen: ¿Cuándo usar cada token?

TokenDuración¿Dónde se usa?¿Para qué?
Access Token60 minutosPeticiones HTTP a APIsAutorizar acceso a recursos
ID Token60 minutosDentro de la appObtener info del usuario (email, nombre)
Refresh Token30 díasPeticiones a CognitoRenovar los otros dos tokens

Flujo completo de una sesión:

09:00 AM – Usuario hace login
          ↓
          Recibe los 3 tokens

09:00 – 10:00 AM – Usuario usa la app
                  ↓
                  Access Token válido
                  API acepta las peticiones

10:00 AM – Access Token expira
          ↓
          App detecta error 401

10:00 AM – App usa Refresh Token
          ↓
          Obtiene nuevos Access + ID tokens

10:00 AM – 11:00 AM – Usuario sigue usando la app
                      ↓
                      Sin interrupciones

[Ciclo se repite cada 60 minutos por 30 días]

Día 30 – Refresh Token expira
        ↓
        Usuario DEBE hacer login otra vez

¿Qué sigue?

En este artículo mostramos los tokens en pantalla solo con fines educativos. En el siguiente artículo implementaremos:

  1. Guardar tokens de forma segura en Keychain
  2. Refresh automático cuando expiren los tokens
  3. Cerrar sesión (Sign Out) y eliminar tokens
  4. Persistencia de sesión – que el usuario no tenga que hacer login cada vez que abre la app

Conclusión

En este artículo implementamos el inicio de sesión completo con AWS Cognito:

SignInView: Pantalla principal de la app con login funcional ✅ Navegación actualizada: La app inicia en login (no en registro) ✅ Configuración de AWS: Habilitamos el flujo USER_PASSWORD_AUTH ✅ Método signIn(): Autenticación real usando InitiateAuth ✅ Recepción de tokens: Access, ID y Refresh tokens funcionando ✅ Visualización educativa: Los tokens se muestran en pantalla para entender cómo funcionan ✅ Manejo de errores: Mensajes específicos para cada tipo de error

Flujo completo implementado:

App inicia → SignInView → Usuario ingresa credenciales →
AWS Cognito valida → Devuelve tokens → Mostrados en pantalla

Ahora tenemos un sistema de autenticación funcional. Los usuarios pueden: – Registrarse (Artículo 1) – Confirmar su cuenta (Artículo 2) – Iniciar sesión (Artículo 3) ✅

En el siguiente artículo vamos a aprender a gestionar estos tokens de forma segura, implementar refresh automático, y cerrar sesión correctamente.

Reflexión final

Te invito a que me sigas en LinkedIn donde comparto contenido relacionado a desarrollo de software, buenas prácticas, y tecnologías AWS. También puedes encontrarme en Medium y explorar mis proyectos en GitHub. Si tienes preguntas o sugerencias, no dudes en conectar – siempre estoy abierto a intercambiar ideas y aprender junto a la comunidad.

Sobre las validaciones

Esta serie de artículos se enfoca en entender cómo funciona AWS Cognito en su forma más pura, por lo que intencionalmente he mantenido las validaciones al mínimo. En un proyecto real de producción, deberías considerar:

Seguridad de tokens: – Guardar tokens en Keychain (nunca en UserDefaults o archivos sin cifrar) – Nunca mostrar tokens en la UI – Implementar refresh automático de tokens – Limpiar tokens al cerrar sesión – Validar expiración de tokens antes de usarlos

Validaciones adicionales: – Validación de formato de email antes de enviar a Cognito – Requisitos de contraseña robusta (mayúsculas, números, símbolos) – Rate limiting para prevenir ataques de fuerza bruta – Timeout en operaciones de red – Manejo de pérdida de conexión – Retry con backoff exponencial

Mejores prácticas: – Usar SRP_AUTH en lugar de USER_PASSWORD_AUTH (más seguro) – Implementar MFA (autenticación multifactor) – Logs de eventos de autenticación – Monitoreo de intentos fallidos – Testing unitario y de integración – Ofuscación de credenciales en el código

El objetivo es que entiendas primero cómo funciona Cognito, y luego puedas aplicar las mejores prácticas de desarrollo según las necesidades de tu proyecto.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *