Autenticación y Seguridad

La API utiliza un sistema de autenticación híbrida que combina Firebase Authentication para la identificación inicial del usuario y un sistema interno de JWT firmado para la autorización y control de acceso dentro del backend. Esto permite una integración segura con aplicaciones cliente como apps móviles que ya usan Firebase, mientras se mantiene el control detallado de permisos en el backend.

Flujo de autenticación

  1. Login en cliente (App) vía Firebase El usuario inicia sesión en la app cliente (con email y contraseña). Firebase devuelve un ID Token firmado que representa la identidad del usuario.

  2. Envía token a la API El cliente envía este token en la cabecera Authorization: Bearer <firebaseToken> al llamar a /auth/login o /auth/register.

  3. Validación del token Firebase en backend La API intercepta estas rutas con un filtro personalizado que:

    • Verifica la validez del token contra Firebase, usando su SDK.

    • Busca el usuario en la base de datos mediante el uid de Firebase.

    • Si el usuario existe, crea un token de autenticación interna, junto a un refresh token (explicado mas adelante).

  4. Generación de un JWT interno (propio) La API genera y devuelve un JWT firmado con claves RSA, que incluye información extendida como:

    • uid

    • role (USER o ADMIN)

    • isPremium (estado del usuario)

  5. Acceso al resto de endpoints Para todas las rutas protegidas, el cliente debe utilizar este nuevo JWT interno, no el token original de Firebase.

Estructura Técnica

🔗 Ver SecurityConfig Completoarrow-up-right

Filtros de seguridad

  • FirebaseAuthenticationFilter: Solo se activa en las rutas /auth/register y /auth/login. Valida el token Firebase recibido en la cabecera y autentica al usuario usando su uid. Código:

  • SecurityFilterChain: Se definen dos filtros distintos:

    • Cadena Pública (@Order(1)): permite el acceso sin JWT interno a /auth/register, /auth/login, /auth/refresh. Usa FirebaseAuthenticationFilter.

    • Cadena Protegida (@Order(2)): todas las demás rutas requieren JWT interno. Validación a través de JwtDecoder con conversión de roles.

    Código:

Conversor de roles

La información de roles se extrae del JWT mediante JwtAuthenticationConverter, mapeando el campo role como prefijo ROLE_ para que Spring Security lo reconozca.

Generación y validación de JWT

  • El backend genera un JWT interno firmado con RSA para representar la sesión del usuario luego de un login exitoso. Este token es utilizado para autenticar todas las rutas protegidas del sistema.

    El JWT contiene claims personalizados como:

    • uid: identificador único del usuario.

    • role: rol de seguridad (USER, ADMIN).

    • isPremium: estado de suscripción premium.

    El token tiene una expiración de 1h y es validado en cada solicitud mediante el JwtDecoder.

Refresh Token: Renovación de Sesiones

Un Refresh Token es un token de larga duración que permite al cliente obtener un nuevo access token (JWT interno) sin necesidad de que el usuario vuelva a iniciar sesión con Firebase.

Esto permite:

  • Mantener la sesión activa sin re-autenticación constante.

  • Minimizar exposición del token principal.

  • Mejorar la experiencia de usuario, ya que si marca "Remember Me" en el cliente, no tiene que autenticarse de nuevo mientras el refresh token sea válido.

Proceso de Emisión

Al realizar login o registro exitosamente, el servidor devuelve dos tokens:

  1. Access Token (JWT interno)

    • Firmado con RSA.

    • Expira en 1h.

  2. Refresh Token

    • Firmado y almacenado.

    • Expira en 7 días.

    • Solo se transmite desde el backend al cliente una vez por sesión.

Almacenamiento Seguro

  • El refresh token es almacenado en la base de datos asociado al uid del usuario.

  • Es de uso único, y cada vez que el usuario vuelve a iniciar sesión, se genera un nuevo token y se reemplaza en la base de datos por el anterior si existiera.

  • En cada logeo, se devuelve el refresh token para que el cliente pueda enviarlo de vuelta en caso de que el

Flujo Completo de Validación y refresco del "Refresh Token"

El cliente inicia sesión marcando "Remember Me":

  • Se valida los datos como ya se ha explicado antes. Si son válidos se devuelve el token JWT y el refresh Token, además de este último guardarse en la base de datos asociado al uid del usuario.

  • Cuando vuelve a entrar a la App, se hace una llamada a auth/validate pasándole el token JWT.

    • Si es válido, si inicia sesion normalmente, usanndo el mismo JWT, sin hace rnada nuevo.

    • Si no es válido (401 Unauthorized), entonces se llama al auth/refresh pasando el refreshtoken que tiene previamente almacenado.

      • Si el refreshtoken es válido, se crea un nuevo JWT, se renueva el refreshtoken de la base de datos, ya que es de uso único, y se devuelve de nuevo el JWT y elñ refreshtoken para que el usuario lo vuelva a almacenar.

      • Si no es válido, devuelve (401 Unauthorized), y en el cliente se obliga a hacer login de nuevo, cerrando la sesión y borrando todos los datos que puediera tener alamcenados.

Para garantizar una autenticación segura y duradera, el sistema utiliza JWTs con expiración corta y refresh tokens almacenados de forma persistente. Los refresh tokens son de un solo uso, se invalidan al ser usados, y están asociados a un usuario específico. Dado que los refresh tokens solo se entregan inicialmente tras un login correcto, y se almacenan en la aplicación del cliente, el sistema asume que su posesión implica legitimidad del usuario, salvo que haya un robo o compromiso del dispositivo, lo cual excede el alcance del sistema actual. Para un entorno real, podrían implementarse controles adicionales como firma de dispositivos, validación del User-Agent, o cookies HttpOnly, pero para el objetivo y alcance del proyecto, he considerado que la protección actual es suficiente y razonablemente segura.

Última actualización