Cuando comenzamos a crear un MVP (minimum viable product o producto mínimo viable), es crítico centrarse en aquellas características que aportan valor. Lo ideal sería no perder un minuto en nada que no tenga que ver con la esencia de la idea que queremos ejecutar. Sin embargo hay ocasiones que, para poder liberar nuestro MVP, debemos implementar características que no van proporcionar feedback alguno pero condicionan la viabilidad del producto.
En el caso de una aplicación multiusuario que esté disponible en la nube necesitaremos registrar e identificar a los usuarios. Es una restricción que no aporta valor al potencial cliente interesado en nuestro producto, pero por lo general lo necesitamos si nuestra aplicación está abierta al público. Aunque el registro y la identificación de usuarios pueden parecer una tarea trivial, hay que tener en cuenta algunas consideraciones:
- Tiene que ser un proceso fácil de seguir: aunque no aporte valor al producto, podría convertirse en una barrera para que los usuarios interesados lleguen a probar el MVP.
- El registro e identificación llevan implícitas muchas tareas no triviales: Encriptación de cookies, encriptación de contraseñas, envío de correo de confirmación, proceso de recuperación de contraseña, generación de tokens, gestión de permisos,…
Existe una extensión de Flask llamada Flask-Security, que propone un proceso de registro, implementa una buena parte de las necesidades referentes a seguridad para aplicaciones web y puede ser integrada y configurada en unas pocas horas. En su página presenta una aplicación mínima que nosotros vamos a extender ligeramente para profundizar un poco más en lo que podría ofrecer para nuestros productos.
Antes de nada, deberíamos establecer nuestro entorno tal y como se explicó en Desarrollando con Flask. Una vez creado el entorno, las dependencias en este caso se instalarían usando:
(env)$ pip install flask-security flask-sqlalchemy flask-mail
El código del ejemplo modificado lo guardaríamos en un fichero app.py de la siguiente manera:
# -*- coding: utf-8 -*-
import os
import datetime
from flask import Flask, render_template
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore, \
UserMixin, RoleMixin, login_required
from flask_mail import Mail
# Create app
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_CONFIRMABLE'] = True
app.config['SECURITY_RECOVERABLE'] = True
# Por defecto sobre gmail
# Se puede utilizar cualquier cuenta de gmail u otra configuración
app.config['MAIL_SERVER'] = os.environ.get('SEC_MAIL_SERVER', 'smtp.gmail.com')
app.config['MAIL_PORT'] = os.environ.get('SEC_MAIL_PORT', 465)
app.config['MAIL_USE_SSL'] = os.environ.get('SEC_MAIL_SSL', True)
app.config['MAIL_USERNAME'] = os.environ.get('SEC_MAIL_USER')
app.config['MAIL_PASSWORD'] = os.environ.get('SEC_MAIL_PASS')
mail = Mail(app)
# Create database connection object
db = SQLAlchemy(app)
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Create a user to test with
@app.before_first_request
def create_user():
db.create_all()
user_datastore.create_user(email='ejemplo@mail.com', password='aaaaaa',
confirmed_at=datetime.datetime.now())
db.session.commit()
# Views
@app.route('/')
@login_required
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run()
Hemos partido del ejemplo implementado con SQLAlchemy por ser el más rápido a la hora de desplegar ya que utiliza SQLite, una base de datos embebida que no necesita instalación.
En el código, por un lado decimos a la extensión que permita el registro de usuarios, la confirmación vía email de los registros y la recuperación de la contraseña en caso de olvido. Por otro, configuramos el servidor de correo para que nos permita el envío de los correos de registro, confirmación y recuperación. La configuración del correo se hace mediante variables de entorno. Esto permite emplear diferentes valores según dónde ejecutemos la aplicación (desarrollo, stage, producción).
Por otro lado hemos creado una página index para comprobar que el usuario se conecta correctamente:
¡Enhorabuena! Estás conectado como
<b>
{{current_user.email}}
</b>
<p>
<a href="{{ url_for_security('logout') }}">Salir</a>
De momento vemos que las páginas son feas, poco explícitas y además sólo están en inglés. En próximos posts veremos cómo cambiar el aspecto de las páginas, traducir los mensajes y alguna característica más de esta potente extensión. De momento, a modo de introducción, nos quedamos con este sencillísimo ejemplo.
Ya está disponible el código fuente completo está en github.