Blog
Flask: Autenticación y registro de usuarios
- Publicado por: Hebert
- Categoría: Blog

Cifrado de Contraseñas con flask
Cuando creamos nuestro modelo de Usuario, incluimos un campo de hash_clave que hasta ahora, no hemos utilizado. El propósito de este campo es almacenar el hash de la contraseña del usuario, el cual será utilizado para verificar la contraseña suministrada por el usuario durante el inicio de sesión. Para cifrar o “hashear” la contraseña tenemos varias formas sencillas de utilizar librerías desde una aplicación.
Werkzeug es uno de los paquetes que implementan el cifrado de contraseñas. Este paquete, es una de las dependencias del núcleo de Flask y por lo tanto ya está instalado en nuestro entorno virtual. Vamos a proceder a crear un usuario en nuestra base de datos con su contraseña encriptada, nos dirigimos a la consola y escribimos flask shell.
>>> from werkzeug.security import check_password_hash as checkph >>> from werkzeug.security import generate_password_hash as genph >>> # Guardamos en una variable llamada hash_clave nuestra contraseña generada por Werkzeug ... hash_clave = genph('12345') >>> # Ahora veamos que contraseña ha generado. ... hash_clave 'pbkdf2:sha256:50000$PLLuA5PF$30a9ef9efd6b6f799e6071b1013ca9fe53e31bcbb60359e9cab6c3c2fe82aa55' >>> # Pueden ver que ha generado una contraseña cifrando nuestra contraseña original '12345' ... # Ahora vamos a decirle que chequee la contraseña cifrada con la original, esto devuelve true si coinciden y false si no. ... checkph(hash_clave, '12345') True >>> # Como vemos las verifica y devuelve true pero si le pasamos un valor distinto devolvera false ... checkph(hash_clave, '123') False >>> # Ahora creemos un usario en la base de datos con esta contraseña cifrada ... nuevoUsuario = Usuario(username = "jose19", email = "josemartinez@gmail.com", hash_clave = hash_clave) >>> bdd.session.add(nuevoUsuario) >>> bdd.session.commit() >>>
Si llegaste hasta aquí y no sabes como ejecutar flask shell ve a la primera parte de este tutorial.
En este ejemplo, la clave ‘12345’ es transformada en una cadena de caracteres muy larga a través de una serie de operaciones criptográficas para las cuales no existe manera (al menos hasta ahora) de revertir, lo que significa que, si por alguna razón alguien obtiene el hash de la clave, los datos seguirán protegidos. Como medida adicional a los procesos de cifrado, si una misma clave es cifrada varias veces, se obtiene un hash distinto, por lo que es también imposible identificar por el hash de la clave si dos usuarios tienen la misma clave.
Por otro lado la función de verificación toma el hash de una contraseña generada previamente, y la contraseña que el usuario ingrese al momento de iniciar sesión. Devuelve True si la contraseña coincide con el hash, y False si no coinciden.
El usuario que hemos creado nos servirá para más adelante.
Toda la lógica de las contraseñas puede ser implementada como dos métodos nuevos en el modelo de los usuarios (modelos.py):
from app import bdd from datetime import datetime from werkzeug.security import generate_password_hash as genph from werkzeug.security import check_password_hash as checkph class Usuario(bdd.Model): id = bdd.Column(bdd.Integer, primary_key=True) username = bdd.Column(bdd.String(64), index=True, unique=True) email = bdd.Column(bdd.String(120), index=True, unique=True) hash_clave = bdd.Column(bdd.String(128)) pubs = bdd.relationship('Pubs', backref='autor', lazy='dynamic') def __repr__(self): return '<Usuario {}>'.format(self.username) def def_clave(self, clave): self.hash_clave = genph(clave) def verif_clave(self, clave): return checkph(self.hash_clave, clave) class Pubs(bdd.Model): id = bdd.Column(bdd.Integer, primary_key=True) cuerpo = bdd.Column(bdd.String(256)) timestamp = bdd.Column(bdd.DateTime, index=True, default=datetime.now) id_usuario = bdd.Column(bdd.Integer, bdd.ForeignKey('usuario.id')) def __repr__(self): return '<Publicación {}>'.format(self.cuerpo)
Flask-Login
En esta parte trabajaremos con una popular extensión de Flask, llamada Flask-Login. Esta extensión maneja el estado de autenticación del usuario, para que los usuarios puedan iniciar sesión en la aplicación y luego navegar por otras paginas mientras la aplicación “recuerda” que el usuario está autenticado. De igual forma provee la funcionalidad de “recuerdame”, que permite que un usuario mantenga su sesión iniciada incluso luego de cerrar el explorador. Para poder iniciar esta parte debemos instalar la extensión en nuestro entorno virtual:
(env) $ pip install flask-login
Al igual que otras extensiones, Flask-Login necesita ser creada e inicializada en el script __init__.py de la siguiente manera:
from flask import Flask from app.settings.config import Ajustes from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager app = Flask(__name__) app.config.from_object(Ajustes) bdd = SQLAlchemy(app) migrate = Migrate(app, bdd) login = LoginManager(app) from app import rutas, modelos
Preparación Usuario/Flask-Login
La extensión trabaja con el modelo de Usuario de la aplicación, y espera que ciertas propiedades y métodos sean implementados en él. Esta aproximación es muy buena, ya que mientras estos elementos necesarios estén en el modelo, Flask-Login no tiene más requerimientos, así que, por ejemplo, puede trabajar con el modelo de usuario de cualquier base de datos.
Los requerimientos son los siguientes:
- is_authenticated (“está autenticado”, en inglés): Booleano cuyo valor es True si el usuario está autenticado y False si no.
- is_active (“está activo”, en inglés): Booleano cuyo valor es True si la cuenta está activa, y False si no.
- is_anonymous (“es anónimo”, en inglés): Booleano cuyo valor es False para los usuarios normales y True para usuarios especiales anónimos.
- get_id() (“obtener id” en inglés): Método que obtiene el valor del id de los usuarios y devuelve como un string (unicode si se está usando Python 2)
Estos cuatro elementos pueden ser fácilmente implementados en nuestros modelos, pero, como la implementación es muy genérica, Flask-Login provee una clase mixta o de mezclas (mixin), llamada UserMixin, la cual incluye implementaciones genéricas que son apropiadas para la mayoría de los modelos de usuarios. Se agrega de la siguiente manera:
Función de Carga de Usuarios
Flask-Login mantiene un registro de los usuarios autenticados, almacenando el id de cada usuario en las sesiones de usuarios de Flask, un espacio de almacenamiento asignado para cada usuario que se conecte a la aplicación. Cada vez que el usuario autenticado navega en una pagina nueva, la extensión recupera el id del usuario de la sesión, y luego carga el usuario a la memoria.
Como Flask-Login no tiene soporte de bases de datos, necesita la ayuda de la aplicación para cargar el usuario. Por esa razón, la extensión espera que la aplicación configure una función de carga de usuarios, que pueda ser llamada para cargar un usuario según su id. Esta función puede ser agregada en el archivo de modelos.py y tambien recuerda de importar a la variable login que creamos anteriormente, nuestro modelos.py debe lucir de la siguiente forma:
from app import bdd, login from datetime import datetime from werkzeug.security import generate_password_hash as genph from werkzeug.security import check_password_hash as checkph from flask_login import UserMixin class Usuario(UserMixin, bdd.Model): id = bdd.Column(bdd.Integer, primary_key=True) username = bdd.Column(bdd.String(64), index=True, unique=True) email = bdd.Column(bdd.String(120), index=True, unique=True) hash_clave = bdd.Column(bdd.String(128)) pubs = bdd.relationship('Pubs', backref='autor', lazy='dynamic') def __repr__(self): return '<Usuario {}>'.format(self.username) def def_clave(self, clave): self.hash_clave = genph(clave) def verif_clave(self, clave): return checkph(self.hash_clave, clave) class Pubs(bdd.Model): id = bdd.Column(bdd.Integer, primary_key=True) cuerpo = bdd.Column(bdd.String(256)) timestamp = bdd.Column(bdd.DateTime, index=True, default=datetime.now) id_usuario = bdd.Column(bdd.Integer, bdd.ForeignKey('usuario.id')) def __repr__(self): return '<Publicación {}>'.format(self.cuerpo) @login.user_loader def cargar_usuario(id): return Usuario.query.get(int(id))
El cargador de usuarios (user_loader) es registrado con el decorador @login.user_loader. El id que Flask-Login pasa a la función como argumento será un string, así que, las bases de datos que usan identificadores numéricos necesitan convertir los strings a enteros como vemos arriba.
Iniciando Sesión
Nuestra plantilla de iniciar_sesion.html debe quedar de la siguiente manera:
{% extends "base.html" %} {% block contenido %} <h1>Iniciar Sesión</h1> <form action="" method="post" novalidate> {{ form.hidden_tag() }} <p> {{ form.nombre.label }}<br> {{ form.nombre(size=32) }}<br> {% for error in form.nombre.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.contraseña.label }}<br> {{ form.contraseña(size=32) }}<br> {% for error in form.contraseña.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p>{{ form.recordar() }} {{ form.recordar.label }}</p> <p>{{ form.enviar() }}</p> </form> {% endblock %}
Revisemos nuevamente la función view de inicio de sesión, en la cual implementamos un inicio de sesión falso que solamente devolvía un mensaje flash(). Ahora que la aplicación tiene acceso a una base de datos de usuarios y sabe como generar y verificar hashes de contraseñas, la función puede ser completada, entonces vayamos a nuestro archivo de rutas.py para darle lógica a nuestro inicio de sesion, debemos recordar import del modelo Usuario para realizar las consultas a la base de datos:
from app import app from flask import render_template, flash, redirect, request, url_for from app.formularios import FormInicio from flask_login import current_user, login_user, login_required from app.modelos import Usuario @app.route('/') @app.route('/index') def index(): usuario = {'nombre':'fede'} pubs = [ { 'autor':{'usuario':'Juan'}, 'pub':'Bonito dia en Barcelona' }, { 'autor':{'usuario':'Maria'}, 'pub':'Hoy tuve una buena tarde en el cine' } ] return render_template('index.html', titulo="Inicio", usuario=usuario, pubs=pubs) @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('index')) form = FormInicio() if form.validate_on_submit(): usuario = Usuario.query.filter_by(username=form.nombre.data).first() if usuario: if usuario.verif_clave(form.contraseña.data): login_user(usuario, remember=form.recordar.data) return redirect(url_for('index')) else: flash('Usuario o contraseña inválido') return redirect(url_for('index')) return render_template('iniciar_sesion.html', title='Iniciar Sesion', form=form)
Antes de continuar quiero decirte que para esta parte es recomendable usar las palabras login y logout tanto en la vista como en la función, ya que flask podría generar un error interno al tratar de renderizar nuestras vistas, en pocas palabras si tu rutas.py luce tal cual como el de arriba entonces no deberías tener problemas.
Las dos primeras lineas de la función login() manejan una situación peculiar. Imaginemos que existe un usuario cuya sesión está iniciada, y otro usuario que abre la dirección /iniciar_sesion de la aplicación. Claramente es un error, asi que queremos evitar que pase. La variable current_user (“usuario actual” en inglés) la cual es una variable propia de Flask_login y puede ser utilizada en cualquier momento para obtener el usuario que representa al cliente que esta logueado actualmente. El valor de esta variable puede ser un usuario de la base de datos (a través del cual Flask-Login obtiene la información para llamar la carga de usuario que definimos anteriormente), o un usuario anónimo especial si el usuario no se ha autenticado aun. Recordemos que definimos en el modelo de usuarios todas esas propiedades que Flask-Login requiere. Una de esas fue is_authenticated, la cual es útil para verificar si el usuario está autenticado o no. Cuando el usuario está autenticado, es redirigido a la pagina de inicio.
En lugar de la llamada a flash() que implementamos antes, ahora el usuario es autenticado realmente. El primer paso es cargar el usuario de la base de datos. El nombre de usuario o username viene en el formulario, así que puede ser utilizado para consultar la base de datos para hallar el usuario. Para este propósito, estamos utilizando el método filter_by() de SQLAlchemy. El resultado de filter_by() es una consulta que solo incluye los objetos que tienen un username que coincida. Como sabemos que solo habrá un resultado o ninguno, se completa la consulta llamando al método first() (primero), el cual devuelve el usuario si existe, o None si no existe. En la cuarta parte vimos que cuando llamamos el método all() en una consulta, la consulta es ejecutada y se obtiene una lista de todos los resultados que coinciden con la consulta. El método first() es utilizado frecuentemente durante las consultas, cuando se quiere obtener solo un resultado.
Si obtenemos una coincidencia para el nombre de usuario que fue suministrado, podemos revisar si la contraseña que también fue suministrada en el formulario es válida. Esto es llevado a cabo con el método verif_clave de la clase Usuario que definimos anteriormente en los modelos.py. Esto toma el hash de la contraseña almacenado previamente con el usuario y determina si la contraseña suministrada coincide con el hash. Ya que tenemos dos condiciones de error posibles: entonces al decirle a python “if usuario:” python entiende que si se obtiene un resultado de la consulta hecha a la base de datos que sea distinto a None que significa vacio, es porque el nombre de usuario es correcto y pasará a la segunda validación la cual es validar la contraseña, esto lo podemos entender a que si el nombre de usuario es incorrecto nos devolvera al formulario de inicio de sesión y si el nombre de usuario es correcto pero la contraseña no, pues ocurrirá lo mismo; el usuario será redirigido al formulario de inicio de sesion.
Si el nombre de usuario y la contraseña son correctas, se llama a la función login_user(), la cual viene de Flask-Login. Esta función registrará el usuario como autenticado, así que cualquier página que visite en el futuro tendrá la variable current_user (usuario actual) definida con ese usuario.
Para finalizar el proceso de autenticación, el usuario es redirigido nuevamente a la página de inicio.
Cerrar la Sesión de un usuario
Lógicamente, si iniciamos sesión, también necesitamos la opción de cerrar la sesión. Esto lo hacemos con la función logout_user() de Flask-Login. Esta sería la función view de cierre de sesión que agregaremos en el archivo rutas.py:
from flask import render_template from app import app from app.formularios import FormInicio from flask import render_template, flash, redirect, url_for, request from flask_login import current_user, login_user, logout_user, login_required from app.modelos import Usuario @app.route('/') @app.route('/index') def index(): usuario = {'nombre':'fede'} pubs = [ { 'autor':{'usuario':'Juan'}, 'pub':'Bonito dia en Barcelona' }, { 'autor':{'usuario':'Maria'}, 'pub':'Hoy tuve una buena tarde en el cine' } ] return render_template('index.html', titulo="Inicio", usuario=usuario, pubs=pubs) @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('index')) form = FormInicio() if form.validate_on_submit(): usuario = Usuario.query.filter_by(username=form.nombre.data).first() if usuario: if usuario.verif_clave(form.contraseña.data): login_user(usuario, remember=form.recordar.data) return redirect(url_for('index')) else: flash('Usuario o contraseña inválido') return redirect(url_for('index')) return render_template('iniciar_sesion.html', title='Iniciar Sesion', form=form) @app.route('/logout') def logout(): logout_user() return redirect(url_for('index'))
Para mostrar el enlace de cierre de sesión a los usuarios, podemos modificar en base.html la barra de navegación para que cambie el enlace de inicio de sesión a uno de cierre de sesión si hay un usuario autenticado:
<html> <head> {% if titulo %} <title>{{ titulo }} - Blog</title> {% else %} <title>Blog</title> {% endif %} </head> <body> <div> Miniblog: <a href="{{ url_for('index') }}">Inicio</a> {% if current_user.is_anonymous %} <a href="{{ url_for('login') }}">Iniciar sesión</a> {% else %} <a href="{{ url_for('logout') }}">Cerrar sesión</a> {% endif %} </div> <hr> {% with messages = get_flashed_messages() %} {% if messages %} <ul> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} {% block contenido %}{% endblock %} </body> </html>
La propiedad is_anonymous es uno de los atributos que Flask-Login agrega a los objetos de los usuarios a través de UserMixin. La expresión current_user.is_anonymous será True solo cuando el usuario no esté autenticado.
Autenticación obligatoria
Flask-Login posee una característica que obliga a los usuarios a iniciar sesión antes de poder ver ciertas partes de la aplicación. Si un usuario que no está autenticado intenta ver una pagina protegida, Flask-Login redirige automaticamente al usuario al formulario de inicio de sesión, y redirigirá a la página que el usuario intentaba ver solo después de que el usuario esté autenticado.
Para que esta característica sea implemetada, Flask-Login necesita saber cual es la función view que maneja los inicios de sesión. Esto puede ser agregado en __init__.py:
from flask import Flask from app.settings.config import Ajustes from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager app = Flask(__name__) app.config.from_object(Ajustes) bdd = SQLAlchemy(app) migrar = Migrate(app,bdd) login = LoginManager(app) login.login_view = 'login' # ESTO ES LO QUE NECESIMATOS """Por último vamos a personalizar el mensaje que genera flask por defecto para cuando un usuario intenta acceder a una página en la que necesita estar logueado y lo hacemos de la siguiente manera""" login.login_message = 'Por favor inicia sesión para acceder a esta página.' from app import rutas, modelos
El valor ‘login’ es el nombre de la función view que maneja el inicio de sesión.
Flask-Login protege las funciones view de usuarios anonimos con un decorador especifico llamado @login_required. Cuando se agrega este decorador a una función luego del decorador @app.route de Flask, la función adquiere protección y no permitirá acceso a usuarios no autenticados. Le aplicaremos dicho decorador a la función index de la siguiente manera:
from flask import render_template from app import app from app.formularios import FormInicio from flask import render_template, flash, redirect, url_for, request from flask_login import current_user, login_user, logout_user, login_required from app.modelos import Usuario from werkzeug.urls import url_parse @app.route('/') @app.route('/index') @login_required def index(): usuario = {'nombre':'fede'} pubs = [ { 'autor':{'usuario':'Juan'}, 'pub':'Bonito dia en Barcelona' }, { 'autor':{'usuario':'Maria'}, 'pub':'Hoy tuve una buena tarde en el cine' } ] return render_template('index.html', titulo="Inicio", usuario=usuario, pubs=pubs) @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('index')) form = FormInicio() if form.validate_on_submit(): usuario = Usuario.query.filter_by(username=form.nombre.data).first() if usuario: if usuario.verif_clave(form.contraseña.data): login_user(usuario, remember=form.recordar.data) return redirect(url_for('index')) else: flash('Usuario o contraseña inválido') return redirect(url_for('index')) return render_template('iniciar_sesion.html', title='Iniciar Sesion', form=form) @app.route('/logout') def logout(): logout_user() return redirect(url_for('index'))
Luego de importar login_required, se agrega el decorador justo antes de comenzar la definición de la función.
Lo que resta ahora es implementar el redireccionamiento a la pagina deseada luego de que el usuario se haya autenticado. Cuando el usuario no está autenticado accede a una función protegida con el decorador, este va a redirigir a la pagina de inicio de sesión, pero incluirá información extra en esta redirección para que la aplicación pueda volver a la pagina que el usuario estaba intentando abrir.
Para manejar mejor nuestras direcciones, utilizaremos una función llamada url_for, la cual, a partir de un string, genera las direcciones URL de la pagina.
Cuando un usuario abre, por ejemplo, la pagina /index/, el decorador, @login_required, interceptará la solicitud y respondera con la redirección a /iniciar_sesion, pero agregará un string al final de la dirección que será utilizado como argumento para hacer una consulta, convirtiendo la dirección de la redirección en /iniciar_sesion?next=next/index. El argumento next tiene el valor de la dirección original para que la aplicación pueda usarla para redirigir luego de el inicio de sesión.
El código para leer y procesar el argumento next, lo agregamos en nuestro archivo rutas.py y debería lucir así:
from flask import render_template from app import app from app.formularios import FormInicio from flask import render_template, flash, redirect, url_for, request from flask_login import current_user, login_user, logout_user, login_required from app.modelos import Usuario from werkzeug.urls import url_parse @app.route('/') @app.route('/index') @login_required def index(): usuario = {'nombre':'fede'} pubs = [ { 'autor':{'usuario':'Juan'}, 'pub':'Bonito dia en Barcelona' }, { 'autor':{'usuario':'Maria'}, 'pub':'Hoy tuve una buena tarde en el cine' } ] return render_template('index.html', titulo="Inicio", usuario=usuario, pubs=pubs) @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('index')) form = FormInicio() if form.validate_on_submit(): usuario = Usuario.query.filter_by(username=form.nombre.data).first() if usuario: if usuario.verif_clave(form.contraseña.data): login_user(usuario, remember=form.recordar.data) next_page = request.args.get('next') if not next_page or url_parse(next_page).netloc != '': next_page = url_for('index') return redirect(next_page) else: flash('Usuario o contraseña inválido') return render_template('iniciar_sesion.html', title='Iniciar Sesion', form=form) @app.route('/logout') def logout(): logout_user() return redirect(url_for('index'))
Justo después de que el usuario haya iniciado sesión con el llamado a la función login_user(), el valor de next es obtenido. Flask suministra una variable llamada request (solicitud en ingles) que contiene toda la información que el cliente envía con la solicitud. En particular, el atributo request.args obtiene el contenido de la dirección y lo pone en un diccionario.
Existen tres casos posibles que deben ser considerados para determinar para donde debe ser la redirección:
- Si la dirección no tiene argumento next, entonces el usuario es redirigido a la pagina de inicio.
- Si la dirección contiene el argumento next definido con una ruta relativa (una dirección sin la parte del dominio), entonces el usuario es redirigido a esa dirección.
- Si la dirección contiene el argumento next definido a una dirección completa que incluye el dominio, entonces el usuario es redirigido a la pagina de inicio.
El primer y segundo caso se explican por si mismos. El tercer caso es para hacer la aplicación mas segura. Un atacante podría insertar una dirección a un sitio web malicioso en el argumento next, de forma que la aplicación redirecciona solamente cuando la dirección es relativa, lo que asegura que la redirección se mantenga dentro del mismo sitio de la aplicación. Para determinar si la dirección es relativa o absoluta, utilizamos la función url_parse() para analizarla y luego revisar si el componente netloc está activo o no.
Mostrar el usuario en la página
Recordemos que al inicio creamos un usuario falso para diseñar la pagina de inicio de la aplicación antes de que el sistema estaba activo. Entonces, como la aplicación tiene usuarios reales ahora, podemos eliminar el usuario falso y comenzar a trabajar con los usuarios reales con current_user de Flask-Login en lugar del usuario falso, nos dirijimos a la plantilla index.html:
{% extends "base.html" %} {% block contenido %} <h1>¡Hola, {{ current_user.username }}!</h1> {% for pub in pubs %} <div><p>{{ pub.autor.usuario }} dice: <b>{{ pub.pub }}</b></p></div> {% endfor %} {% endblock %}
Y podemos eliminar el usuario y las publicaciones falsas de la vista index de nuesto archivo rutas.py:
from flask import render_template from app import app from app.formularios import FormInicio from flask import render_template, flash, redirect, url_for, request from flask_login import current_user, login_user, logout_user, login_required from app.modelos import Usuario from werkzeug.urls import url_parse @app.route('/') @app.route('/index') @login_required def index(): return render_template('index.html', titulo='Inicio') @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('index')) form = FormInicio() if form.validate_on_submit(): usuario = Usuario.query.filter_by(username=form.nombre.data).first() if usuario: if usuario.verif_clave(form.contraseña.data): login_user(usuario, remember=form.recordar.data) next_page = request.args.get('next') if not next_page or url_parse(next_page).netloc != '': next_page = url_for('index') return redirect(next_page) else: flash('Usuario o contraseña inválido') return render_template('iniciar_sesion.html', titulo='Iniciar Sesion', form=form) @app.route('/logout') def logout(): logout_user() return redirect(url_for('index'))
Es un buen momento para probar como se comportan las funcionalidades de autenticación. Ya que aun no hay opciones de registro de usuarios, ¿recuerdas el usuario que creamos en al inicio de este capitulo?, pues ahora lo usaremos para iniciar sesión.
Si abrimos la aplicación e intentamos acceder a /index seremos inmediatamente redirigidos a la pagina de inicio de sesión, y luego de iniciar sesión con las credenciales que definimos anteriormente, podremos abrir la página en la cual veremos un saludo personalizado.
Registro de usuarios
La ultima pieza que ensamblaremos en esta parte es el formulario de registro, para que los usuarios puedan registrarse a si mismos. Vamos a formularios.py:
from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import DataRequired, ValidationError, Email, EqualTo # ... class FormRegistro(FlaskForm): username = StringField('Nombre de Usuario', validators=[DataRequired()]) email = StringField('Email', validators=[DataRequired(), Email()]) contraseña = PasswordField('Contraseña', validators=[DataRequired()]) contraseña2 = PasswordField('Repita su Contraseña', validators=[DataRequired(),EqualTo('contraseña', 'Las contraseñas no coinciden')]) submit = SubmitField('Register')
Tenemos varios elementos interesantes en el nuevo formulario. Primeramente, para el campo de email, agregamos un segundo validador luego de DataRequired, llamado Email. Este es otro validador que viene con WTForms, el cual asegura que lo que el usuario ingrese en el campo coincida con la estructura de una dirección de correo electrónico.
Como este es un formulario de registro, es costumbre solicitar la contraseña dos veces para reducir el riesgo de errores. Por esa razón, tenemos dos campos para la contraseña (contraseña y contraseña2). El segundo campo de contraseña utiliza otro validador llamado EqualTo, el cual asegura que el valor de ambos campos de contraseñas sean iguales.
Tenemos ademas dos métodos nuevos en la clase llamados validar_username() y validar_email(). Cuando agregamos algún método para verificar el patrón de verificación, WTForms los toma como validadores personalizados y los llama en adición a los validadores. En este caso queremos asegurar que el nombre de usuario y sus correos no estén en la base de datos, de forma que estos dos métodos consultan la base de datos para verificar que no haya resultados. En el caso de que haya un resultado, sale un error de validación (ValidationError). El mensaje incluido como argumento en la excepción, será el mensaje que el usuario recibirá junto al campo del formulario.
Para mostrar el formulario en la página, necesitamos la plantilla HTML, la cual se llamará registro.html y estará en la carpeta de plantillas, app/templates. Su estructura será la siguiente:
{% extends "base.html" %} {% block contenido %} <h1>Registro</h1> <form action="" method="post"> {{ form.hidden_tag() }} <p> {{ form.username.label }}<br> {{ form.username(size=30) }}<br> {% for error in form.username.errors %} <span style="color:red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.email.label }}<br> {{ form.email(size=60) }}<br> {% for error in form.email.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.contraseña.label }}<br> {{ form.contraseña(size=32) }}<br> {% for error in form.contraseña2.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.contraseña2.label }}<br> {{ form.contraseña2(size=30) }}<br> {% for error in form.contraseña2.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p>{{ form.submit() }}</p> </form> {% endblock %}
La plantilla iniciar_sesion.html necesita entonces un enlace que lleve al usuario al formulario de registro, justo después del botón enviar:
# ··· <p><a href="{{ url_for('registro') }}">Registro</a></p>
Finalmente, necesitamos escribir la función view que manejará el registro de usuarios nuevos en rutas.py:
from app import app, bdd from app.formularios import FormInicio, FormRegistro # ··· @app.route('/registro', methods=['GET','POST']) def registro(): if current_user.is_authenticated: return redirect(url_for('index')) form = FormRegistro() if form.validate_on_submit(): usuario = Usuario(username=form.username.data, email=form.email.data) usuario.def_clave(form.contraseña.data) bdd.session.add(usuario) bdd.session.commit() flash('Usuario registrado correctamente, ahora puedes iniciar sesión.') return redirect(url_for('login')) return render_template('registro.html', titulo='Registro', form=form)
Esta función view es facilmente entendible. Al inicio nos aseguramos de que el usuario que visite esta la dirección no esté autenticado. El formulario es manejado de la misma forma que el de inicio de sesión. La lógica dentro de el condicional if validate_on_submit() crea un nuevo usuario con la información suministrada por el formulario, lo escribe en la base de datos y luego redirecciona a la pagina de inicio de sesión para que el usuario pueda iniciar sesión.
Con estos cambios, los usuarios están capacitados para crear cuentas en la aplicación, iniciar sesión y cerrar sesión. Tenemos que asegurarnos de intentar todas las validaciones que agregamos en el formulario de registro para entender mejor como funcionan. Mas adelante volveremos al sistema de autenticación para agregar funcionalidades como permitir al usuario cambiar su contraseña si fue olvidada.
Recuerda que anteriormente creamos un usuario a través de la consola con ayuda de flask shell pero a partir de este punto ya es posible registrar usuarios en nuestro blog a través del mismo, pruébalo.
➡ Continúa aprendiendo con nuestro Curso de Flask – Python: