Confirmación de cuenta con código de verificación en AWS Cognito (iOS)

En el artículo anterior implementamos el registro de usuarios con AWS Cognito. Al registrarse, el usuario recibe un email con un código de 6 dígitos para confirmar su cuenta. En este artículo vamos a implementar la pantalla y funcionalidad para que el usuario pueda ingresar ese código y activar su cuenta.

Como siempre, mantendremos la implementación clara, sencilla y accesible. Nos enfocaremos en entender cómo funciona el proceso de confirmación en Cognito.

¿Qué vamos a construir?

Una pantalla de confirmación que permita: – Ingresar el código de 6 dígitos recibido por email – Confirmar el código con AWS Cognito – Reenviar el código si no llegó o expiró – Manejar errores comunes (código incorrecto, expirado) – Verificar que la cuenta quede activada en AWS

Requisitos previos

  • Haber completado el artículo anterior (Registro de usuarios)
  • Tener un usuario registrado en estado UNCONFIRMED
  • Haber recibido el email con el código de verificación

Paso 1: Creando la interfaz de confirmación

Primero, vamos a crear una vista simple y limpia para que el usuario ingrese su código. Creamos un archivo llamado ConfirmCodeView.swift:

import SwiftUI

struct ConfirmCodeView: View {
    let email: String  // Email del usuario que se registró

    @State private var code: String = ""
    @State private var isLoading: Bool = false
    @State private var message: String = ""
    @State private var showAlert: Bool = false

    var body: some View {
        VStack(spacing: 20) {
            // Título
            Text("Confirmar Cuenta")
                .font(.largeTitle)
                .fontWeight(.bold)
                .padding(.bottom, 10)

            // Instrucciones
            Text("Ingresa el código de 6 dígitos que enviamos a:")
                .font(.body)
                .foregroundColor(.gray)
                .multilineTextAlignment(.center)

            Text(email)
                .font(.body)
                .fontWeight(.semibold)
                .foregroundColor(.blue)
                .padding(.bottom, 20)

            // Campo de código
            TextField("Código de verificación", text: $code)
                .keyboardType(.numberPad)
                .textContentType(.oneTimeCode)  // Autocompletar desde SMS/Email
                .padding()
                .background(Color(.systemGray6))
                .cornerRadius(10)
                .multilineTextAlignment(.center)
                .font(.title2)
                .disabled(isLoading)

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

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

            // Botón de Reenviar código
            Button(action: {
                resendCode()
            }) {
                Text("Reenviar código")
                    .font(.body)
                    .foregroundColor(.blue)
            }
            .disabled(isLoading)
            .padding(.top, 10)

            Spacer()
        }
        .padding(.horizontal, 30)
        .padding(.top, 50)
        .alert("Confirmación", isPresented: $showAlert) {
            Button("OK", role: .cancel) { }
        } message: {
            Text(message)
        }
    }

    // MARK: - Función de Confirmación
    private func confirmCode() {
        // Validación básica
        guard !code.isEmpty else {
            message = "Por favor ingresa el código"
            showAlert = true
            return
        }

        guard code.count == 6 else {
            message = "El código debe tener 6 dígitos"
            showAlert = true
            return
        }

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

        // Por ahora, solo simulamos
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            isLoading = false
            message = "Código confirmado exitosamente"
            showAlert = true
        }
    }

    // MARK: - Función de Reenvío
    private func resendCode() {
        // Aquí irá la lógica para reenviar el código
        message = "Código reenviado. Revisa tu email."
        showAlert = true
    }
}

¿Qué hace este código?

Parámetros de entrada: – email: Recibe el email del usuario para mostrarlo en pantalla

Estados locales (@State): – code: Almacena el código de 6 dígitos que ingresa el usuario – isLoading: Controla el indicador de carga – message: Mensajes para el usuario – showAlert: Controla cuándo mostrar alertas

Elementos de la interfaz:

1. Título: “Confirmar Cuenta”

2. Instrucciones: Texto explicativo con el email del usuario

3. Campo de código: – .keyboardType(.numberPad): Teclado numérico – .textContentType(.oneTimeCode): iOS puede autocompletar desde emails/SMS – .multilineTextAlignment(.center): Código centrado – .font(.title2): Texto grande y legible

4. Botón “Confirmar Código”: Con spinner cuando está procesando

5. Botón “Reenviar código”: Por si el código no llegó o expiró

Validaciones básicas: – Verifica que el código no esté vacío – Verifica que tenga exactamente 6 dígitos

Por ahora: Las funciones confirmCode() y resendCode() solo simulan el comportamiento. En el siguiente paso las conectaremos con AWS Cognito.

Paso 2: Navegación automática desde el registro

Para que el usuario llegue a esta pantalla de confirmación después de registrarse, necesitamos configurar la navegación. Vamos a hacer que después de un registro exitoso, la app navegue automáticamente a ConfirmCodeView.

2.1 Actualizar RegisterView

Primero, agregamos los estados necesarios para controlar la navegación en RegisterView.swift:

struct RegisterView: View {
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var confirmPassword: String = ""
    @State private var isLoading: Bool = false
    @State private var message: String = ""
    @State private var showAlert: Bool = false
    @State private var navigateToConfirmation: Bool = false  // Nuevo
    @State private var registeredEmail: String = ""          // Nuevo

    private let authService = CognitoAuthService()

¿Qué agregamos? – navigateToConfirmation: Controla cuándo navegar a la pantalla de confirmación – registeredEmail: Guarda el email del usuario para pasarlo a ConfirmCodeView

2.2 Agregar navigationDestination

Al final de la vista, antes del cierre del último }, agregamos el modifier de navegación:

 .alert("Registro", isPresented: $showAlert) {
            Button("OK", role: .cancel) { }
        } message: {
            Text(message)
        }
        .navigationDestination(isPresented: $navigateToConfirmation) {
            ConfirmCodeView(email: registeredEmail)
        }
    }

¿Qué hace .navigationDestination? – Cuando navigateToConfirmation es true, navega a ConfirmCodeView – Pasa el registeredEmail como parámetro

2.3 Actualizar la función de registro

Modificamos la parte donde manejamos el registro exitoso:

Task {
    do {
        let result = try await authService.signUp(email: email, password: password)

        await MainActor.run {
            isLoading = false
            message = result

            // Guardar el email antes de limpiar
            registeredEmail = email

            // Limpiar campos
            email = ""
            password = ""
            confirmPassword = ""

            // Navegar a pantalla de confirmación
            navigateToConfirmation = true
        }
    } catch {
        // ... manejo de errores
    }
}

¿Qué cambió? – Ahora guarda el email en registeredEmail antes de limpiarlo – Activa navigateToConfirmation = true para disparar la navegación – Ya no muestra una alerta de éxito, sino que navega directamente

2.4 Actualizar ContentView

Para que la navegación funcione, necesitamos envolver RegisterView en un NavigationStack:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            RegisterView()
        }
    }
}

Paso 3: Probando el flujo completo

Ahora si ejecutas la app en el simulador:

  • Registro:
    • Ingresa tu email real
    • Ingresa una contraseña válida (ej: 1@wAs1234)Confirma la contraseña
    • Click en “Registrarse”
  • Navegación automática:
    • Después de registrarse exitosamente
    • La app automáticamente navega a ConfirmCodeView
    • Verás tu email mostrado en la pantalla
  • Pantalla de confirmación:
    • Campo para ingresar código de 6 dígitos
    • Botón “Confirmar Código”
    • Botón “Reenviar código”

    Nota: Por ahora los botones solo simulan el comportamiento (el de confirmación espera 2 segundos y muestra un mensaje). En el siguiente paso los conectaremos con AWS Cognito para que funcionen de verdad.

    Paso 4: Implementando la confirmación real con AWS Cognito

    Ahora vamos a conectar los botones con AWS Cognito para que confirmen y reenvíen códigos de verdad.

    4.1 Agregar métodos al servicio de autenticación

    Abrimos CognitoAuthService.swift y agregamos dos nuevos métodos después del método signUp():

    Método 1: Confirmar código

    // MARK: - Confirmación de Usuario
    func confirmSignUp(email: String, code: String) async throws -> String {
        guard let client = cognitoClient else {
            throw NSError(domain: "CognitoAuthService", code: -1,
                         userInfo: [NSLocalizedDescriptionKey: "Cliente de Cognito no inicializado"])
        }
    
        // Crear la solicitud de confirmación
        let confirmInput = ConfirmSignUpInput(
            clientId: clientId,
            confirmationCode: code,
            username: email
        )
    
        do {
            let response = try await client.confirmSignUp(input: confirmInput)
    
            print("✅ Código confirmado exitosamente")
            print("Response: \(response)")
    
            return "Cuenta confirmada exitosamente. Ahora puedes iniciar sesión"
    
        } catch {
            // Manejar errores de Cognito
            print("❌ Error al confirmar: \(error)")
    
            let errorMessage = error.localizedDescription
            if errorMessage.contains("CodeMismatchException") {
                throw NSError(domain: "CognitoAuthService", code: 2001,
                            userInfo: [NSLocalizedDescriptionKey: "Código incorrecto. Verifica e intenta de nuevo"])
            } else if errorMessage.contains("ExpiredCodeException") {
                throw NSError(domain: "CognitoAuthService", code: 2002,
                            userInfo: [NSLocalizedDescriptionKey: "El código ha expirado. Solicita uno nuevo"])
            } else {
                throw NSError(domain: "CognitoAuthService", code: 2000,
                            userInfo: [NSLocalizedDescriptionKey: "Error al confirmar: \(errorMessage)"])
            }
        }
    }

    Qué hace este método? – Recibe el email del usuario y el code de 6 dígitos – Crea un ConfirmSignUpInput con estos datos – Llama a client.confirmSignUp() de AWS Cognito – Si es exitoso, retorna mensaje de confirmación – Si hay error, identifica el tipo: – CodeMismatchException: Código incorrecto – ExpiredCodeException: Código expirado – Otros errores genéricos

    Método 2: Reenviar código

    // MARK: - Reenviar Código de Confirmación
    func resendConfirmationCode(email: String) async throws -> String {
        guard let client = cognitoClient else {
            throw NSError(domain: "CognitoAuthService", code: -1,
                         userInfo: [NSLocalizedDescriptionKey: "Cliente de Cognito no inicializado"])
        }
    
        // Crear la solicitud de reenvío
        let resendInput = ResendConfirmationCodeInput(
            clientId: clientId,
            username: email
        )
    
        do {
            let response = try await client.resendConfirmationCode(input: resendInput)
    
            print("✅ Código reenviado exitosamente")
            print("Destino: \(response.codeDeliveryDetails?.destination ?? "N/A")")
    
            return "Código reenviado. Revisa tu email"
    
        } catch {
            // Manejar errores de Cognito
            print("❌ Error al reenviar código: \(error)")
            throw NSError(domain: "CognitoAuthService", code: 3000,
                         userInfo: [NSLocalizedDescriptionKey: "Error al reenviar código: \(error.localizedDescription)"])
        }
    }
    

    ¿Qué hace este método? – Recibe solo el email del usuario – Crea un ResendConfirmationCodeInput – Llama a client.resendConfirmationCode() de AWS Cognito – AWS envía un nuevo código al email del usuario – Retorna mensaje de confirmación

    4.2 Actualizar ConfirmCodeView

    Ahora conectamos la vista con el servicio. Primero agregamos la instancia del servicio en ConfirmCodeView.swift:

    struct ConfirmCodeView: View {
        let email: String
    
        @State private var code: String = ""
        @State private var isLoading: Bool = false
        @State private var message: String = ""
        @State private var showAlert: Bool = false
    
        private let authService = CognitoAuthService()  // Nuevo

    Luego actualizamos la función confirmCode():

    // MARK: - Función de Confirmación
    private func confirmCode() {
        // Validación básica
        guard !code.isEmpty else {
            message = "Por favor ingresa el código"
            showAlert = true
            return
        }
    
        guard code.count == 6 else {
            message = "El código debe tener 6 dígitos"
            showAlert = true
            return
        }
    
        // Confirmar con AWS Cognito
        isLoading = true
        message = "Confirmando..."
    
        Task {
            do {
                let result = try await authService.confirmSignUp(email: email, code: code)
    
                await MainActor.run {
                    isLoading = false
                    message = result
                    showAlert = true
    
                    // Limpiar el código
                    code = ""
                }
            } catch {
                await MainActor.run {
                    isLoading = false
                    message = error.localizedDescription
                    showAlert = true
                }
            }
        }
    }

    ¿Qué cambió? – Ya no es una simulación – Llama al método real authService.confirmSignUp() – Usa Task para llamadas asíncronas – Maneja errores reales de AWS Cognito – Limpia el código después de confirmación exitosa

    Y actualizamos la función resendCode():

    // MARK: - Función de Reenvío
    private func resendCode() {
        isLoading = true
        message = "Reenviando código..."
    
        Task {
            do {
                let result = try await authService.resendConfirmationCode(email: email)
    
                await MainActor.run {
                    isLoading = false
                    message = result
                    showAlert = true
                }
            } catch {
                await MainActor.run {
                    isLoading = false
                    message = error.localizedDescription
                    showAlert = true
                }
            }
        }
    }

    ¿Qué cambió? – Llama al método real authService.resendConfirmationCode() – Muestra mensajes reales de AWS Cognito

    4.3 Probando la confirmación real

    Ejecuta la app y prueba el flujo completo:

    1. Registro:
      1. Ingresa tu email
      1. Ingresa contraseña válida
      1. Click en “Registrarse”
    2. Navegación automática:
      1. La app navega a pantalla de confirmación
      1. Verás tu email mostrado
    3. Confirmación:
      1. Ingresa el código de 6 dígitos de tu email
      1. Click en “Confirmar Código”
      1. Verás: “Cuenta confirmada exitosamente. Ahora puedes iniciar sesión”

    Errores posibles: – Si ingresas código incorrecto: “Código incorrecto. Verifica e intenta de nuevo” – Si el código expiró: “El código ha expirado. Solicita uno nuevo”

    Probando “Reenviar código”: – Click en “Reenviar código” – Recibirás un nuevo código por email – Verás: “Código reenviado. Revisa tu email”

    4.4 Verificar en AWS Console

    Para confirmar que todo funcionó:

    1. Ve a AWS Console → Cognito → User pools
    2. Abre tu User Pool
    3. Ve a “Users”
    4. Busca tu usuario

    Deberías ver: – Account status: CONFIRMED ✅ – Email verified: Yes ✅

    Si antes estaba UNCONFIRMED, ahora está CONFIRMED. La cuenta está activada y lista para iniciar sesión.

    Conclusión

    En este artículo implementamos el flujo completo de confirmación de cuenta con AWS Cognito:

    Interfaz de confirmación: Una pantalla simple y limpia para ingresar el código de 6 dígitos

    Navegación automática: Después del registro, la app lleva al usuario directamente a confirmar su cuenta

    Confirmación real: Conectamos la app con AWS Cognito usando confirmSignUp()

    Reenvío de código: Implementamos la opción de solicitar un nuevo código si el primero expiró o no llegó

    Manejo de errores: Distinguimos entre código incorrecto, código expirado, y otros errores

    Verificación en AWS: Confirmamos que el usuario pasa de UNCONFIRMED a CONFIRMED

    Ahora los usuarios pueden registrarse y confirmar sus cuentas de forma completa. En el siguiente artículo vamos a implementar el inicio de sesión (Sign In) para que los usuarios confirmados puedan autenticarse y obtener sus tokens de acceso.

    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:

    • Validaciones más robustas de email (formato, dominios válidos)
    • Validación de contraseña en el cliente antes de enviar a AWS
    • Manejo de estados de UI más sofisticado (loading, success, error)
    • Reintentos automáticos en caso de fallos de red
    • Logs y monitoreo de errores
    • Timeout en operaciones de red
    • Validación de rate limiting
    • Ofuscación de credenciales en el código
    • Gestión segura de tokens
    • Testing unitario y de integración

    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 *