+-

Implementar la autenticación de dos factores (2FA

Aportación por MCE. Mauricio Eberle Morales, Jul 23, 2024

Tema anterior - Siguiente tema

MCE. Mauricio Eberle Morales

Implementar la autenticación de dos factores (2FA) en una aplicación web utilizando Python y Flask. Este tutorial cubrirá los siguientes pasos:

  • Configuración del entorno.
  • Creación de una aplicación Flask básica.
  • Implementación del registro y el inicio de sesión de usuarios.
  • Integración de la autenticación de dos factores usando un autenticador de tiempo basado en tokens (TOTP).

1. Configuración del entorno
Primero, necesitas instalar algunas dependencias. Asegúrate de tener Python y pip instalados en tu sistema. Luego, instala Flask y otras bibliotecas necesarias.

pip install Flask Flask-SQLAlchemy Flask-WTF pyotp qrcode[pil]

2. Creación de una aplicación Flask básica
Crea una estructura de directorios básica para tu aplicación Flask:

your_project/
    app.py
    templates/
        base.html
        login.html
        register.html
        two_factor.html
    static/

app.py

from flask import Flask, render_template, redirect, url_for, request, flash, session
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, EqualTo
import pyotp
import qrcode
import qrcode.image.svg
from io import BytesIO

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), nullable=False, unique=True)
    password = db.Column(db.String(150), nullable=False)
    otp_secret = db.Column(db.String(16), nullable=False)

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=2, max=150)])
    password = PasswordField('Password', validators=[DataRequired(), Length(min=6)])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Register')

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Login')

class TwoFactorForm(FlaskForm):
    token = StringField('Token', validators=[DataRequired()])
    submit = SubmitField('Verify')

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        otp_secret = pyotp.random_base32()
        new_user = User(username=form.username.data, password=form.password.data, otp_secret=otp_secret)
        db.session.add(new_user)
        db.session.commit()
        flash('Account created! Please log in.', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and user.password == form.password.data:
            session['username'] = user.username
            session['otp_secret'] = user.otp_secret
            return redirect(url_for('two_factor'))
        else:
            flash('Login Unsuccessful. Please check username and password', 'danger')
    return render_template('login.html', form=form)

@app.route('/two_factor', methods=['GET', 'POST'])
def two_factor():
    form = TwoFactorForm()
    otp_secret = session.get('otp_secret')
    if not otp_secret:
        return redirect(url_for('login'))
    if form.validate_on_submit():
        user = User.query.filter_by(username=session.get('username')).first()
        totp = pyotp.TOTP(user.otp_secret)
        if totp.verify(form.token.data):
            session['authenticated'] = True
            flash('Logged in successfully!', 'success')
            return redirect(url_for('dashboard'))
        else:
            flash('Invalid token. Please try again.', 'danger')
    return render_template('two_factor.html', form=form)

@app.route('/dashboard')
def dashboard():
    if not session.get('authenticated'):
        return redirect(url_for('login'))
    return f"Welcome to your dashboard, {session['username']}!"

@app.route('/qr_code')
def qr_code():
    otp_secret = session.get('otp_secret')
    if not otp_secret:
        return redirect(url_for('login'))
    totp = pyotp.TOTP(otp_secret)
    img = qrcode.make(totp.provisioning_uri(session.get('username'), issuer_name="YourApp"))
    buffer = BytesIO()
    img.save(buffer, format='PNG')
    buffer.seek(0)
    return buffer.getvalue(), 200, {'Content-Type': 'image/png'}

if __name__ == '__main__':
    db.create_all()
    app.run(debug=True)

templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2FA Tutorial</title>
</head>
<body>
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            {% for category, message in messages %}
                <div class="alert alert-{{ category }}">{{ message }}</div>
            {% endfor %}
        {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
</body>
</html>

templates/register.html

{% extends "base.html" %}
{% block content %}
    <form method="POST" action="">
        {{ form.hidden_tag() }}
        <div>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </div>
        <div>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </div>
        <div>
            {{ form.confirm_password.label }}<br>
            {{ form.confirm_password(size=32) }}<br>
            {% for error in form.confirm_password.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </div>
        <div>
            {{ form.submit() }}
        </div>
    </form>
{% endblock %}

templates/login.html

{% extends "base.html" %}
{% block content %}
    <form method="POST" action="">
        {{ form.hidden_tag() }}
        <div>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </div>
        <div>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </div>
        <div>
            {{ form.submit() }}
        </div>
    </form>
{% endblock %}

templates/two_factor.html

{% extends "base.html" %}
{% block content %}
    <img src="{{ url_for('qr_code') }}" alt="QR Code"><br>
    Scan the QR code above with your authenticator app and enter the token below.<br>
    <form method="POST" action="">
        {{ form.hidden_tag() }}
        <div>
            {{ form.token.label }}<br>
            {{ form.token(size=32) }}<br>
            {% for error in form.token.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </div>
        <div>
            {{ form.submit() }}
        </div>
    </form>
{% endblock %}

3. Implementación del registro y el inicio de sesión de usuarios
El código anterior ya incluye la lógica básica para el registro y el inicio de sesión de usuarios.

4. Integración de la autenticación de dos factores usando un autenticador de tiempo basado en tokens (TOTP)
La parte crucial de este tutorial es la integración de la autenticación de dos factores usando TOTP. En el código anterior, pyotp se utiliza para generar y verificar tokens de tiempo basados en secretos únicos para cada usuario.

Ejecución de la aplicación
Para ejecutar la aplicación, asegúrate de estar en el directorio del proyecto y ejecuta:

python app.py

Abre tu navegador y visita http://127.0.0.1:5000/register para registrar un nuevo usuario y seguir el flujo de trabajo de la autenticación de dos factores.

Aportación rápida

Nota: este mensaje no se mostrará hasta que sea aprobado por un moderador.

Nombre:
Correo electrónico:
Atajos: ALT+S para publicar/enviar o ALT+P para previsualizar

Open Access

Únete a nosotros en nuestro compromiso de promover el acceso abierto y la difusión del conocimiento. Tu apoyo financiero nos permite continuar con nuestros proyectos de Open Access. Además, como agradecimiento, recibirás una hermosa página web como regalo. ¡Juntos podemos hacer la diferencia en el mundo del conocimiento abierto!

Powered by EzPortal