Protección de Rutas y Autenticación Persistente en Flutter con AWS Cognito
Introducción
Este artículo es parte de una serie en la que estaré explorando distintas tecnologías de AWS aplicadas al desarrollo móvil. Desde que comencé a colaborar como AWS Community Builder, he sentido la responsabilidad de compartir conocimiento sobre herramientas clave, no solo para quienes ya tienen experiencia con AWS, sino también para aquellos que están dando sus primeros pasos en la plataforma. Mi objetivo es hacer que la integración de AWS en Flutter sea accesible y comprensible, eliminando barreras técnicas que puedan parecer complicadas al inicio.
En artículos anteriores, ya hemos configurado la autenticación en una app Flutter con AWS Cognito y Amplify. Sin embargo, la autenticación no se trata solo de iniciar sesión, sino también de controlar el acceso a ciertas pantallas y mantener la sesión iniciada incluso después de cerrar la app. Aquí es donde entran dos conceptos fundamentales:
En este artículo, no nos enfocaremos en patrones de arquitectura avanzada ni en la optimización del código. La idea es mantener la implementación simple y práctica, de modo que cualquier persona, sin importar su nivel de experiencia, pueda seguir los pasos sin dificultades.
Autenticación de Rutas → Evita que usuarios no autenticados accedan a pantallas protegidas.
Autenticación Persistente → Permite que los usuarios no tengan que estar iniciando sesión cada que cambian de pantalla
A medida que avancemos en la serie, iremos incrementando la complejidad, incorporando mejores prácticas de desarrollo móvil y explorando más servicios de AWS. Pero por ahora, enfoquémonos en lo esencial: proteger nuestras rutas en Flutter y mantener la autenticación de usuario con AWS Cognito. ¡Comencemos! 🚀
¿Que vamos a crear?
En este ejemplo, trabajaremos con dos nuevas pantallas que llamaremos Ruta 1 y Ruta 2.
Ruta 1 representará una pantalla de detalle, aunque no contendrá ninguna funcionalidad específica.
Ruta 2 servirá como una pantalla de ayuda, pero al igual que la anterior, solo mostrará un texto de referencia.
Ambas pantallas incluirán un mensaje simple, como un «Hola Mundo», únicamente para indicar en cuál de ellas se encuentra el usuario en ese momento.


Para interactuar con estas pantallas, agregaremos dos botones en HomeScreen
, la pantalla a la que accedemos tras iniciar sesión correctamente. Los botones estarán etiquetados como Ruta 1 y Ruta 2, y funcionarán de la siguiente manera:
- Si el usuario está autenticado, el botón de Ruta 1 lo llevará directamente a Ruta 2.
- Si el usuario NO está autenticado, el botón de Ruta 1 lo dirigirá a Ruta 1.
- El botón de Ruta 2 siempre llevará al usuario a Ruta 2, sin importar su estado de autenticación.

Para lograr esto, necesitaremos verificar si la sesión del usuario sigue activa. Esta validación se implementará dentro de nuestro servicio de autenticación, lo que nos permitirá determinar si el usuario tiene acceso a Ruta 2 o si debe ser redirigido a Ruta 1.
Como mencionamos anteriormente, mantendremos todo lo más simple posible. Aunque en un proyecto más estructurado lo ideal sería organizar el código en múltiples archivos, en este caso realizaremos el registro, inicio de sesión y cierre de sesión dentro de un solo archivo. Esto nos permitirá centrarnos en la integración de AWS Amplify y Cognito, evitando distracciones con arquitecturas más avanzadas.
¿Que vamos a usar?
Antes de comenzar, asegúrate de contar con lo siguiente:
1 Una cuenta de AWS activa
AWS (Amazon Web Services) es una plataforma en la nube que proporciona soluciones para almacenamiento, cómputo, bases de datos, inteligencia artificial y más. En este caso, utilizaremos AWS Cognito y Amplify para manejar la autenticación y persistencia de sesiones en nuestra app Flutter.
Si aún no tienes una cuenta de AWS, puedes registrarte en aws.amazon.com y aprovechar el nivel gratuito para comenzar sin costo.
2 Flutter (Proyecto base configurado)
Flutter es un framework de UI multiplataforma desarrollado por Google que permite construir aplicaciones nativas para iOS, Android, web y escritorio con un solo código en Dart.
Si aún no lo tienes instalado, puedes seguir la guía oficial en flutter.dev para configurarlo en tu sistema. También necesitarás un proyecto Flutter ya creado para implementar la autenticación y protección de rutas.
3 AWS Amplify (Instalado y Configurado)
AWS Amplify es un conjunto de herramientas que simplifica la integración de AWS en aplicaciones móviles y web. Nos permite conectar nuestra app con servicios como Cognito, DynamoDB, S3 y API Gateway sin necesidad de configuraciones complejas.
En este proyecto, ya hemos instalado y configurado Amplify CLI para manejar la autenticación con Cognito, por lo que solo agregaremos nuevas validaciones para gestionar la sesión del usuario de manera persistente.
4 AWS Cognito (Autenticación y Validación de Sesión)
AWS Cognito es un servicio de gestión de usuarios, autenticación y autorización. Nos permite:
✅ Registrar e iniciar sesión con credenciales como correo y contraseña.
✅ Administrar tokens de autenticación y sesiones.
✅ Controlar accesos a distintos servicios de AWS.
Hasta ahora, hemos utilizado Cognito para manejar el registro, inicio de sesión y cierre de sesión en nuestra aplicación. En este artículo, agregaremos una validación adicional que nos permitirá verificar si la sesión del usuario sigue activa para mantenerlo autenticado sin necesidad de volver a iniciar sesión manualmente.
Implementación de los botones en la Interfaz
Agregaremos los dos botones, que será bastante sencillo, recordemos que no estamos preocupados por lo visual en este momento.
Row(
children: [
ElevatedButton(
onPressed: () async {
bool isLoggedIn = await _authService.checkUserSession();
if (isLoggedIn) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
const HelpScreen()), // Usuario autenticado -> Ruta 2
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
const DetailScreen()), // Usuario no autenticado -> Ruta 1
);
}
},
child: const Text("Ruta 1"),
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const HelpScreen(
// email: _usernameController.text.trim(),
)),
);
},
child: const Text("Ruta 2"),
),
],
)
Botón de «Ruta 1» verifica si el usuario está autenticado.
- Si está autenticado, lo redirige a
HelpScreen
(Ruta 2). - Si NO está autenticado, lo redirige a
DetailScreen
(Ruta 1).
Botón de «Ruta 2» verifica. Este botón siempre permite navegar a HelpScreen
sin validación.
Este sería el código completo del archivo home_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_aws_auth/details_screen.dart';
import 'package:flutter_aws_auth/help_screen.dart';
import 'authentication_service.dart';
class HomeScreen extends StatefulWidget {
HomeScreen({super.key, required this.email});
final String email;
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _authService = AuthenticationService();
final _codeController = TextEditingController();
final _newPasswordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Inicio")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_authService.signOut();
},
child: const Text("Cerrar Sesión"),
),
const SizedBox(height: 20),
TextField(
controller: _codeController,
decoration: InputDecoration(labelText: "Código de verificación"),
),
TextField(
controller: _newPasswordController,
decoration: InputDecoration(labelText: "Nueva Contraseña"),
obscureText: true,
),
SizedBox(height: 20),
TextButton(
onPressed: () async {
print(widget.email);
await _authService.resetPassword(widget.email);
},
child: const Text("Recuperar contraseña"),
),
SizedBox(height: 20),
TextButton(
onPressed: () async {
await _authService.confirmResetPassword(
widget.email,
_codeController.text.trim(),
_newPasswordController.text.trim(),
);
// WidgetToRenderBoxAdapter
// print("TextButton presionado");
},
child: const Text("Confirmar nueva contraseña"),
),
Row(
children: [
ElevatedButton(
onPressed: () async {
bool isLoggedIn = await _authService.checkUserSession();
if (isLoggedIn) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
const HelpScreen()), // Usuario autenticado -> Ruta 2
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
const DetailScreen()), // Usuario no autenticado -> Ruta 1
);
}
},
child: const Text("Ruta 1"),
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const HelpScreen(
// email: _usernameController.text.trim(),
)),
);
},
child: const Text("Ruta 2"),
),
],
)
],
),
),
);
}
}
Como ya hemos visto casi todo este código en artículos anteriores, lo explicamos de manera general
El código define una pantalla llamada HomeScreen
, que se muestra cuando el usuario inicia sesión en la aplicación. Esta pantalla permite:
- Cerrar sesión con AWS Cognito.
- Restablecer y confirmar una nueva contraseña.
- Navegar entre dos pantallas (
DetailScreen
yHelpScreen
) según el estado de autenticación del usuario.
Implementación del método que verifica sesión
Método checkUserSession()
Future<bool> checkUserSession() async {
try {
var user = await Amplify.Auth.getCurrentUser();
print("checkUserSession");
print(user.signInDetails);
return user != null; // Si el usuario existe, está autenticado
} catch (e) {
return false; // Si hay error, el usuario NO está autenticado
}
}
Future<bool>
→ Indica que la función es asíncrona y devuelve un valor de tipo bool
.
Propósito: Determinar si el usuario tiene una sesión activa.
Usa Amplify.Auth.getCurrentUser()
para obtener la información del usuario autenticado en Cognito.
Si el usuario está autenticado, user
contendrá detalles de la sesión.
Si el usuario NO está autenticado o la sesión expiró, se genera una excepción (catch
).
Esto solo lo agregue para verificar con una impresión rápida lo que sucede, pero al final sabemos que puede ser un Log de manera más apropiada.print("checkUserSession")
→ Muestra en consola que la función ha sido llamada.print(user.signInDetails)
→ Muestra detalles de la sesión del usuario (como método de
Si user
es diferente de null
, significa que hay una sesión activa → Retorna true
.
Si user
es null
o se genera un error, significa que no hay usuario autenticado → Retorna false
.
Este ejemplo puede fallar si:
El usuario cerró sesión → Amplify.Auth.getCurrentUser()
lanzará una excepción.
La sesión expiró → AWS Cognito invalida la sesión y se necesita reautenticación.
Error de conexión → Si la app no puede comunicarse con AWS, la función también devolverá false
.
En resumen podemos decir:
Propósito: Verifica si el usuario está autenticado en AWS Cognito.
Retorna: true
si la sesión sigue activa, false
si no.
Uso: Para redirigir automáticamente al usuario según su estado de sesión.
Manejo de errores: Si hay un error o la sesión caducó, devuelve false
.
Aunque el resto del código ya lo tenemos debido a que se hizo en artículos anteriores, comparto el código completo, archivo authentication_service.dart :
import 'package:amplify_flutter/amplify_flutter.dart';
class AuthenticationService {
Future<String> signUp(String email, String password) async {
try {
SignUpResult result = await Amplify.Auth.signUp(
username: email,
password: password,
options: SignUpOptions(userAttributes: {
// 'email': email,
AuthUserAttributeKey.email: email,
}),
);
if (result.nextStep.signUpStep == "confirmSignUp") {
print("Usuario necesita confirmar su cuenta.");
return "confirmSignUp"; // Devuelve este string si se requiere confirmación
}
print("Usuario registrado correctamente.");
return "signedUp"; // Usuario registrado correctamente
} catch (e) {
print("Error en el registro: $e");
return "error"; // Devuelve "error" en caso de excepción
}
}
/// Confirmar el registro del usuario con el código de verificación enviado al email
Future<void> confirmSignUp(String email, String confirmationCode) async {
try {
SignUpResult result = await Amplify.Auth.confirmSignUp(
username: email,
confirmationCode: confirmationCode,
);
print("Usuario confirmado correctamente.");
} catch (e) {
print("Error al confirmar usuario: $e");
}
}
Future<String> signIn(String username, String password) async {
print('Iniciando sesión');
print(username + password);
try {
SignInResult result = await Amplify.Auth.signIn(
username: username,
password: password,
);
if (result.isSignedIn) {
print("Inicio de sesión exitoso.");
return "signedIn"; // Esto activa la redirección a HomeScreen
} else if (result.nextStep.signInStep == "confirmSignUp") {
print("Usuario necesita confirmar su cuenta.");
return "confirmSignUp";
} else {
print("Inicio de sesión incompleto.");
return "incomplete";
}
} catch (e) {
print("Error en el inicio de sesión: $e");
return "error";
}
}
/// Enviar código de recuperación al correo del usuario
Future<void> resetPassword(String email) async {
try {
ResetPasswordResult result =
await Amplify.Auth.resetPassword(username: email);
print("Código enviado a $email");
} catch (e) {
print("Error al enviar código: $e");
}
}
Future<void> confirmResetPassword(
String email, String code, String newPassword) async {
try {
await Amplify.Auth.confirmResetPassword(
username: email,
newPassword: newPassword,
confirmationCode: code,
);
print("Contraseña restablecida correctamente.");
} catch (e) {
print("Error al restablecer contraseña: $e");
}
}
Future<bool> checkUserSession() async {
try {
var user = await Amplify.Auth.getCurrentUser();
print("checkUserSession");
print(user.signInDetails);
return user != null; // Si el usuario existe, está autenticado
} catch (e) {
return false; // Si hay error, el usuario NO está autenticado
}
}
Future<void> signOut() async {
try {
await Amplify.Auth.signOut();
print('Sesión cerrada');
} catch (e) {
print('Error al cerrar sesión: $e');
}
}
}
Pantallas nuevas
Desde el inicio del artículo hemos mencionado la necesidad de trabajar con rutas, por lo que ahora crearemos dos nuevas pantallas: Ruta 1 y Ruta 2.
Ruta 1 representará una pantalla de detalles, aunque solo será una simulación sin contenido funcional.
Ruta 2 servirá como una pantalla de ayuda, también con un propósito meramente ilustrativo.
Ambas pantallas serán sencillas, conteniendo únicamente un texto centrado en la pantalla para facilitar su identificación y diferenciar en cuál se encuentra el usuario en cada momento. Comenzaremos creando el primer archivo.
Archivo details_screen.dart
import 'package:flutter/material.dart';
class DetailScreen extends StatefulWidget {
const DetailScreen({super.key});
@override
State<StatefulWidget> createState() => _DetailScreenState();
}
class _DetailScreenState extends State<DetailScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Inicio")),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text("Ruta 1 Details")],
),
),
);
}
}
Este código define una pantalla llamada DetailScreen
, que es una pantalla sencilla en Flutter con un título y un texto centrado en la pantalla. Importa el paquete flutter/material.dart
, que contiene los widgets y componentes necesarios para construir la interfaz de usuario en Flutter.
DetailScreen
es un StatefulWidget
, lo que significa que puede cambiar de estado durante su ciclo de vida.const DetailScreen({super.key})
→ Define el constructor de la pantalla, permitiendo que reciba una key
opcional.createState()
→ Crea y devuelve una instancia de _DetailScreenState
, que es el estado de la pantalla
Extiende State<DetailScreen>
, lo que indica que este es el estado asociado a DetailScreen
.
build(BuildContext context)
→ Construye la interfaz de usuario cada vez que el estado cambia.
Scaffold
→ Es el widget base que define la estructura visual de la pantalla.AppBar(title: Text("Inicio"))
→ Agrega una barra superior con el título "Inicio"
.Center
→ Centra el contenido en la pantalla.Column
→ Organiza los elementos verticalmente.mainAxisAlignment: MainAxisAlignment.center
→ Asegura que los elementos se ubiquen en el centro de la pantalla.Text("Ruta 1 Details")
→ Muestra un mensaje indicando que el usuario está en «Ruta 1 Details».
Resumen
DetailScreen
es una pantalla simple que muestra un texto centrado.- Usa
Scaffold
para la estructura yAppBar
para el título. - La pantalla está diseñada para ser fácilmente identificable en la navegación de la app.
Ahora vamos con el segunda archivo que podemos decir es casi una copia identiva, archivo help_screen.dart:
import 'package:flutter/material.dart';
class HelpScreen extends StatefulWidget {
const HelpScreen({super.key});
@override
State<StatefulWidget> createState() => _HelpScreenState();
}
class _HelpScreenState extends State<HelpScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Inicio")),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text("Ruta 2 Help")],
),
),
);
}
}
Como vemos es identico el 95% del ´codigo, pero el texto es diferente, solo para diferenciarlo.
Resumen General
En este ejercicio, hemos implementado protección de rutas y autenticación persistente en una aplicación Flutterutilizando AWS Cognito. La navegación en la app sigue un flujo que permite controlar el acceso a ciertas pantallas según el estado de autenticación del usuario.
Flujo de Navegación
Inicio de sesión y redirección automática
- Al abrir la app, verificamos si el usuario ya está autenticado con
Amplify.Auth.getCurrentUser()
. - Si está autenticado, lo enviamos a
HomeScreen
. - Si no está autenticado, lo redirigimos a
LoginScreen
.
Navegación desde HomeScreen
- Se presentan dos botones: Ruta 1 (
DetailScreen
) y Ruta 2 (HelpScreen
). - El botón de Ruta 1 tiene protección de autenticación:
- Si el usuario está autenticado, lo lleva a Ruta 2.
- Si no está autenticado, lo mantiene en Ruta 1.
- El botón de Ruta 2 siempre está disponible, permitiendo navegar sin restricciones.
Manejo de Cierre de Sesión
- Si el usuario presiona el botón «Cerrar Sesión», su sesión se invalida con
Amplify.Auth.signOut()
. - Luego, es redirigido a
LoginScreen
, eliminando todas las pantallas anteriores en la pila de navegación.
Objetivo del Ejercicio
El objetivo principal de este ejercicio es demostrar cómo implementar protección de rutas y autenticación persistente en Flutter utilizando AWS Cognito, asegurando que los usuarios solo puedan acceder a ciertas pantallas si están autenticados.
Este concepto es esencial en el desarrollo de aplicaciones móviles modernas, donde la seguridad y la gestión de sesiones son clave para garantizar una buena experiencia de usuario
Bueno, mi nombre es José Luján, desarrollador con 20 años de experiencia en el mundo del desarrollo, mobile, IA y crypto. Si tienes sugerencias sobre temas que te gustaría ver en los próximos artículos, serán bienvenidas.
Nos vemos pronto en la siguiente entrega, y si quieres seguir en contacto, me puedes encontrar en cualquier red social como @josedlujan. 🚀 ¡Hasta la próxima!