Flask

Flask

Tutorial Flask: Autenticación con Flask-Login

Flask: Aprende a configurar y gestionar sesiones de usuario de manera segura con Flask-Login en tu aplicación para garantizar autenticación efectiva.

Aprende Flask y certifícate

Configuración y gestión de sesiones

Para implementar autenticación en una aplicación Flask, es fundamental configurar correctamente Flask-Login y gestionar adecuadamente las sesiones de usuario. Flask-Login simplifica el proceso de manejo de sesiones, permitiendo que la aplicación recuerde qué usuarios han iniciado sesión entre peticiones.

Primero, instala Flask-Login en tu entorno virtual ejecutando:

pip install flask-login

A continuación, inicializa la extensión en tu aplicación:

from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)
app.secret_key = 'tu_clave_secreta'  # Reemplaza con una clave segura

login_manager = LoginManager()
login_manager.init_app(app)

Es crucial definir una clave secreta en app.secret_key para que Flask pueda manejar las sesiones de forma segura. Esta clave debe ser única y mantenerse en secreto para garantizar la integridad de las cookies de sesión.

El objeto LoginManager es responsable de gestionar la autenticación y las sesiones de usuario. Al inicializarlo con login_manager.init_app(app), se integra con tu aplicación Flask para proporcionar funcionalidades de inicio de sesión.

Es necesario definir una función de carga de usuario que permita a Flask-Login recuperar la información del usuario a partir del identificador almacenado en la sesión:

from your_app.models import User  # Importa tu modelo de usuario

@login_manager.user_loader
def load_user(user_id):
    return User.get(user_id)

En este ejemplo, User es el modelo que representa a los usuarios en tu base de datos. La función load_user utiliza el user_id para recuperar la instancia del usuario correspondiente.

Para mejorar la seguridad de las sesiones, puedes configurar opciones adicionales:

login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'  # Ruta a la vista de inicio de sesión
login_manager.remember_cookie_duration = timedelta(days=7)
login_manager.refresh_view = 'auth.reauthenticate'
login_manager.needs_refresh_message = 'Por seguridad, por favor vuelve a iniciar sesión.'
  • session_protection: Añade protección contra ataques de secuestro de sesión. Los niveles pueden ser 'basic' o 'strong'.
  • login_view: Especifica la ruta de la vista que maneja el inicio de sesión, utilizada para redirigir a usuarios no autenticados.
  • remember_cookie_duration: Establece la duración de la cookie para la función "Recordarme".
  • refresh_view: Define la vista a la que se redirige cuando la sesión necesita ser refrescada.
  • needs_refresh_message: Mensaje mostrado cuando es necesario reautenticar al usuario.

Para asegurar que las sesiones sean manejadas correctamente, es importante configurar los parámetros relacionados con las cookies:

app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Lax'
)
  • SESSION_COOKIE_SECURE: Garantiza que las cookies solo se envíen a través de conexiones HTTPS.
  • SESSION_COOKIE_HTTPONLY: Evita que las cookies sean accesibles mediante JavaScript, protegiendo contra ataques XSS.
  • SESSION_COOKIE_SAMESITE: Controla si las cookies se envían en solicitudes cruzadas, añadiendo protección contra ataques CSRF.

Además, para personalizar el comportamiento y los mensajes relacionados con la autenticación, puedes ajustar las siguientes configuraciones:

login_manager.login_message = 'Necesitas iniciar sesión para acceder a esta página.'
login_manager.login_message_category = 'alert'
login_manager.needs_refresh_message_category = 'info'
  • login_message: Personaliza el mensaje mostrado a los usuarios no autenticados.
  • login_message_category: Define la categoría del mensaje para fines de estilización en las plantillas.
  • needs_refresh_message_category: Categoría del mensaje cuando se requiere refrescar la sesión.

Para mantener una gestión eficiente de las sesiones, es recomendable manejar correctamente los inicios y cierres de sesión utilizando las funciones proporcionadas por Flask-Login:

from flask_login import login_user, logout_user
  • login_user(user): Inicia sesión del usuario especificado y configura la sesión.
  • logout_user(): Finaliza la sesión actual y limpia los datos relacionados.

La configuración cuidadosa de Flask-Login y la gestión de sesiones es esencial para garantizar la seguridad y una experiencia de usuario fluida en tu aplicación Flask. Asegúrate de revisar estas configuraciones y adaptarlas según las necesidades específicas de tu proyecto.

Definición del modelo de usuario

Para gestionar la autenticación en Flask con Flask-Login, es esencial definir un modelo de usuario que interactúe con la base de datos y cumpla con los requisitos de la extensión. Este modelo representará a los usuarios registrados y proporcionará los métodos necesarios para la autenticación y el manejo de sesiones.

El modelo de usuario se define como una clase que extiende de db.Model de SQLAlchemy, y representa la tabla users en la base de datos. A continuación se muestra un ejemplo completo del modelo de usuario:

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

db = SQLAlchemy()

class User(db.Model, UserMixin):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), unique=True, nullable=False)
    email = db.Column(db.String(255), unique=True, nullable=False)
    password_hash = db.Column(db.String(128), nullable=False)
    active = db.Column(db.Boolean, default=True)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

En este modelo:

  • id: Es la clave primaria que identifica de forma única a cada usuario.
  • username: Es el nombre de usuario, único y no nulo.
  • email: Almacena el correo electrónico del usuario, único y no nulo.
  • password_hash: Guarda la contraseña en forma de hash seguro.
  • active: Indica si el usuario está activo; por defecto es True.

La clase User hereda de UserMixin, que proporciona implementaciones por defecto de las propiedades y métodos que Flask-Login requiere:

  • is_authenticated: Retorna True si el usuario está autenticado.
  • is_active: Retorna True si la cuenta del usuario está activa.
  • is_anonymous: Retorna False para usuarios autenticados.
  • get_id(): Retorna el identificador del usuario en forma de cadena.

Al heredar de UserMixin, nos aseguramos de que el modelo cumple con las interfaces necesarias para que Flask-Login pueda interactuar correctamente con los usuarios.

Es importante gestionar las contraseñas de forma segura. En lugar de almacenar las contraseñas en texto plano, utilizamos las funciones generate_password_hash y check_password_hash de Werkzeug para crear y verificar hashes seguros.

Para crear un nuevo usuario y establecer su contraseña, se puede utilizar:

nuevo_usuario = User(username='usuario1', email='usuario1@example.com')
nuevo_usuario.set_password('contraseña_secreta')
db.session.add(nuevo_usuario)
db.session.commit()

La función set_password genera un hash seguro de la contraseña proporcionada y lo almacena en password_hash. Para verificar la contraseña durante el inicio de sesión, se utiliza check_password:

usuario = User.query.filter_by(username='usuario1').first()
if usuario and usuario.check_password('contraseña_introducida'):
    login_user(usuario)

Aquí, login_user es la función de Flask-Login que inicia la sesión del usuario después de verificar correctamente sus credenciales.

Además, es posible extender el modelo de usuario con atributos adicionales según las necesidades de la aplicación, como roles, perfiles o permisos.

No olvides inicializar la extensión SQLAlchemy y configurar la aplicación para que utilice la base de datos:

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://usuario:contraseña@localhost/mi_base_datos'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)

Es esencial asegurarse de que la configuración de la base de datos es correcta y que la aplicación está conectada a ella. Al trabajar con modelos y bases de datos, es buena práctica utilizar migraciones para gestionar los cambios en los esquemas de las tablas. Flask-Migrate facilita este proceso y se integra bien con SQLAlchemy.

La definición adecuada del modelo de usuario es fundamental para el correcto funcionamiento de la autenticación en Flask. Al seguir estas prácticas, garantizamos una implementación segura y eficiente del manejo de usuarios en nuestra aplicación.

Decorador @login_required y rutas protegidas

El decorador @login_required es una herramienta esencial proporcionada por Flask-Login para proteger rutas que requieren que el usuario esté autenticado. Al aplicarlo a una vista, se asegura de que solo los usuarios que han iniciado sesión puedan acceder a esa ruta específica.

Para utilizar el decorador, es necesario importarlo desde flask_login:

from flask_login import login_required

A continuación, se aplica el decorador @login_required a las funciones de vista que se deseen proteger:

@app.route('/perfil')
@login_required
def perfil():
    return render_template('perfil.html')

En este ejemplo, la ruta /perfil está protegida. Si un usuario no autenticado intenta acceder a esta ruta, será redirigido automáticamente a la página de inicio de sesión definida en login_manager.login_view.

Es importante resaltar que el orden de los decoradores es significativo. Si se combinan varios decoradores en una función de vista, @login_required debe colocarse después de @app.route y antes de cualquier otro decorador personalizado.

El comportamiento predeterminado al redirigir a usuarios no autenticados se configura mediante el LoginManager al inicializar la aplicación. Por ejemplo:

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'

Aquí, login_view especifica la ruta de la vista de inicio de sesión. Cuando un usuario sin iniciar sesión intenta acceder a una ruta protegida, es redirigido a auth.login.

Además, es posible personalizar el mensaje flash que se muestra al usuario mediante login_message y establecer su categoría con login_message_category:

login_manager.login_message = 'Por favor, inicia sesión para acceder a esta página.'
login_manager.login_message_category = 'info'

Si se desea que ciertas rutas sean accesibles tanto para usuarios autenticados como no autenticados, se pueden omitir el decorador @login_required en esas vistas. No obstante, dentro de la función de vista, es posible verificar si el usuario está autenticado utilizando current_user.is_authenticated:

from flask_login import current_user

@app.route('/inicio')
def inicio():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    return render_template('inicio.html')

En este ejemplo, si el usuario ya ha iniciado sesión, es redirigido al dashboard; de lo contrario, se le muestra la página de inicio.

Para proteger rutas con métodos HTTP específicos, es necesario asegurarse de que el decorador @login_required se aplique correctamente. Por ejemplo, en una ruta que admite tanto GET como POST:

@app.route('/configuracion', methods=['GET', 'POST'])
@login_required
def configuracion():
    if request.method == 'POST':
        # Procesar los datos del formulario
        pass
    return render_template('configuracion.html')

El decorador @login_required garantiza que solo los usuarios autenticados puedan tanto visualizar el formulario como enviar datos mediante POST.

En el caso de utilizar Blueprints, el decorador @login_required sigue aplicándose de la misma manera. Supongamos que tenemos un Blueprint llamado panel:

from flask import Blueprint
from flask_login import login_required

panel = Blueprint('panel', __name__)

@panel.route('/dashboard')
@login_required
def dashboard():
    return render_template('dashboard.html')

Es fundamental asegurarse de que el Blueprint esté registrado correctamente en la aplicación principal.

Si se requiere proteger todas las rutas dentro de un Blueprint o un grupo de rutas, se puede aplicar @login_required a nivel de Blueprint utilizando una clase LoginRequiredMixin o aplicando el decorador en la definición de las rutas.

Para manejar los casos en que se necesita redirigir a usuarios no autenticados a diferentes vistas según la ruta solicitada, se puede utilizar el parámetro next en la URL. Al intentar acceder a una ruta protegida, Flask-Login añade un parámetro next que contiene la ruta original. En la vista de inicio de sesión, es posible redirigir al usuario de vuelta a la ruta deseada después de autenticarse:

from flask import request, redirect, url_for

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # Autenticar al usuario
        if usuario_valido:
            login_user(usuario)
            next_page = request.args.get('next')
            return redirect(next_page or url_for('inicio'))
    return render_template('login.html')

Aquí, después de una autenticación exitosa, la aplicación redirige al usuario a la página original solicitada si existe el parámetro next; de lo contrario, lo redirige a la página de inicio.

Es importante validar y asegurar el parámetro next para prevenir ataques de redirección abierta. Se recomienda utilizar la función is_safe_url para validar las URLs:

from urllib.parse import urlparse, urljoin
from flask import request

def is_safe_url(target):
    ref_url = urlparse(request.host_url)
    test_url = urlparse(urljoin(request.host_url, target))
    return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc

Luego, en la vista de inicio de sesión:

from flask import request, redirect, url_for

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # Autenticar al usuario
        if usuario_valido:
            login_user(usuario)
            next_page = request.args.get('next')
            if not is_safe_url(next_page):
                return abort(400)
            return redirect(next_page or url_for('inicio'))
    return render_template('login.html')

Esta práctica garantiza que la aplicación solo redirige a URLs seguras dentro del mismo dominio.

El decorador @login_required también puede utilizarse en métodos de clases cuando se trabaja con vistas basadas en clases (CBVs). En este caso, es necesario utilizar la función method_decorator de Flask:

from flask.views import MethodView
from flask_login import login_required

class PerfilView(MethodView):
    decorators = [login_required]

    def get(self):
        return render_template('perfil.html')

En este ejemplo, todas las peticiones a PerfilView estarán protegidas y requerirán que el usuario esté autenticado.

Es posible combinar @login_required con otros decoradores personalizados para manejar permisos y roles más avanzados. Por ejemplo:

from functools import wraps
from flask import abort
from flask_login import current_user

def admin_required(f):
    @wraps(f)
    @login_required
    def decorated_function(*args, **kwargs):
        if not current_user.is_admin:
            return abort(403)
        return f(*args, **kwargs)
    return decorated_function

@app.route('/admin')
@admin_required
def admin_panel():
    return render_template('admin.html')

Aquí, solo los usuarios autenticados con privilegios de administrador pueden acceder a la ruta /admin.

Al proteger rutas con @login_required, se mejora la seguridad de la aplicación al garantizar que solo usuarios autenticados accedan a ciertas funcionalidades. Es una práctica recomendada en el desarrollo de aplicaciones web con Flask y contribuye a una gestión eficiente de la autenticación.

Cierre de sesión y manejo de logout

El cierre de sesión es una parte crucial en la gestión de usuarios autenticados en una aplicación Flask. Con Flask-Login, el proceso de cerrar sesión es sencillo y seguro utilizando la función logout_user(). Esta función revoca la sesión del usuario actual y limpia cualquier dato asociado a la autenticación.

Para implementar el logout, primero hay que importar logout_user desde flask_login:

from flask_login import logout_user

A continuación, se define una ruta para el cierre de sesión. Es común que esta ruta sea accesible mediante un método POST para evitar que los enlaces puedan provocar cierres de sesión accidentales. Sin embargo, también puede ser una ruta con método GET si se manejan adecuadamente las consideraciones de seguridad:

@app.route('/logout')
@login_required
def logout():
    logout_user()
    flash('Has cerrado sesión correctamente.', 'success')
    return redirect(url_for('inicio'))

En este ejemplo, la función logout() realiza las siguientes acciones:

  • Llama a logout_user(), que elimina la sesión del usuario.
  • Muestra un mensaje flash al usuario indicando que ha cerrado sesión.
  • Redirige al usuario a la página de inicio utilizando redirect y url_for.

Es importante proteger la ruta de logout con el decorador @login_required para asegurar que solo los usuarios autenticados puedan acceder a ella. No obstante, en este caso es aceptable dejar la ruta sin proteger, ya que llamar a logout_user() en un usuario no autenticado no genera errores relevantes; sin embargo, por coherencia y seguridad, es preferible mantener el decorador.

Si se desea implementar el cierre de sesión utilizando el método POST, se puede modificar la ruta de la siguiente manera:

@app.route('/logout', methods=['POST'])
@login_required
def logout():
    logout_user()
    flash('Has cerrado sesión correctamente.', 'success')
    return redirect(url_for('inicio'))

Y en la plantilla, se puede añadir un formulario para el botón de logout:

<form action="{{ url_for('logout') }}" method="post">
    <button type="submit">Cerrar sesión</button>
</form>

Esta práctica añade una capa adicional de seguridad, evitando que robots o enlaces manipulados puedan forzar un cierre de sesión no intencionado.

Es fundamental asegurarse de que el formulario de logout está protegido contra ataques CSRF (Cross-Site Request Forgery). Para ello, se puede utilizar Flask-WTF y su soporte para CSRF:

Primero, instalar Flask-WTF:

pip install flask-wtf

Configurar la clave secreta en la aplicación para los formularios:

app.config['SECRET_KEY'] = 'otra_clave_secreta'

En el formulario de logout, añadir el token CSRF:

<form action="{{ url_for('logout') }}" method="post">
    {{ form.csrf_token }}
    <button type="submit">Cerrar sesión</button>
</form>

En la vista, utilizar una clase de formulario vacía si es necesario:

from flask_wtf import FlaskForm

class EmptyForm(FlaskForm):
    pass

@app.route('/logout', methods=['POST'])
@login_required
def logout():
    form = EmptyForm()
    if form.validate_on_submit():
        logout_user()
        flash('Has cerrado sesión correctamente.', 'success')
        return redirect(url_for('inicio'))
    else:
        abort(400)

Esta implementación asegura que solo las solicitudes POST legítimas desde la aplicación pueden cerrar la sesión del usuario.

Alternativamente, si se prefiere evitar la complejidad del manejo de formularios para el logout, se puede utilizar el método POST con AJAX o utilizar un enlace con el método GET, recordando las implicaciones de seguridad.

Es recomendable limpiar cualquier dato de sesión o variable específica del usuario al cerrar sesión. Aunque logout_user() maneja la mayor parte de este proceso, si la aplicación almacena información adicional en la sesión, es buena práctica eliminarla:

from flask import session

@app.route('/logout')
@login_required
def logout():
    logout_user()
    session.clear()
    flash('Has cerrado sesión correctamente.', 'success')
    return redirect(url_for('inicio'))

La función session.clear() elimina todos los datos almacenados en la sesión, garantizando que no quedan residuos que puedan afectar a la próxima sesión.

Además, es posible personalizar el comportamiento después del logout. Por ejemplo, redirigir al usuario a una página específica o realizar acciones adicionales como registrar el evento en un log:

import logging

@app.route('/logout')
@login_required
def logout():
    user = current_user
    logout_user()
    logging.info(f'El usuario {user.username} ha cerrado sesión.')
    flash('Has cerrado sesión correctamente.', 'success')
    return redirect(url_for('inicio'))

Si la aplicación utiliza Blueprints, el proceso es similar. Supongamos que tenemos un Blueprint llamado auth:

from flask import Blueprint, redirect, url_for, flash
from flask_login import logout_user, login_required

auth = Blueprint('auth', __name__)

@auth.route('/logout')
@login_required
def logout():
    logout_user()
    flash('Has cerrado sesión correctamente.', 'success')
    return redirect(url_for('main.index'))

En este caso, se asegura de redirigir al usuario a la ruta correcta utilizando el nombre del Blueprint en url_for.

Algunas consideraciones importantes al manejar el logout:

  • Siempre proporcionar retroalimentación al usuario indicando que la sesión se ha cerrado.
  • Proteger la ruta con @login_required para evitar comportamientos inesperados.
  • Considerar las implicaciones de seguridad al elegir entre métodos GET y POST.
  • Manejar adecuadamente los datos de sesión y limpiar cualquier información sensible.

Es clave asegurar que la interfaz de usuario refleja el estado de autenticación. Por ejemplo, mostrar u ocultar elementos del menú dependiendo de si el usuario está autenticado o no:

{% if current_user.is_authenticated %}
    <p>Bienvenido, {{ current_user.username }}</p>
    <form action="{{ url_for('logout') }}" method="post">
        {{ form.csrf_token }}
        <button type="submit">Cerrar sesión</button>
    </form>
{% else %}
    <a href="{{ url_for('login') }}">Iniciar sesión</a>
    <a href="{{ url_for('register') }}">Registrarse</a>
{% endif %}

De esta forma, mejoramos la experiencia del usuario y mantenemos una navegación coherente en la aplicación.

Aprende Flask online

Otras lecciones de Flask

Accede a todas las lecciones de Flask y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Flask y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  1. Configurar Flask-Login para manejar la autenticación y sesiones de usuario.
  2. Implementar rutas protegidas con el decorador @login_required.
  3. Crear un modelo de usuario seguro utilizando SQLAlchemy y Flask-Login.
  4. Gestionar el inicio y cierre de sesión manteniendo la seguridad de la aplicación.
  5. Configurar cookies y opciones avanzadas de seguridad para sesiones.
  6. Personalizar vistas y mensajes de autenticación.