domingo, 6 de septiembre de 2020

Flask 17. Manejo de Cookies en Flask.

 


Anteriormente. Flask 16. Mostrando mensajes en Flask.


Manejo de Cookies en Flask.


Cuando estamos creando una aplicación o proyecto en Flask se nos puede dar el caso de que tengamos que guardar provisionalmente alguna información en el lado del cliente, es decir que necesitemos gestionar algún dato que nos proporciona el usuario pero desde su lado, sin subirlo al servidor. 

¿Quien no conoce el clásico caso de los carritos de la compra de las páginas web de venta de artículos online?. En ellos seleccionas los productos que quieres comprar y los incluyes en el carrito, para más tarde completar el proceso de compra. Incluso si cierras el navegador y vuelves a esa página más tarde, tu carrito de la compra seguirá ahí conteniendo los productos que ya habías seleccionado. 

Esto se consigue con las cookies. Tal como nos pone la wikipedia: 

"Una cookie es un término que hace referencia a una pequeña información enviada por un sitio web y almacenada en el navegador del usuario, de manera que el sitio web puede consultar la actividad previa del navegador." 

En resumen es una pequeña información que guardamos en el navegador del usuario información que no sea sensible y que después desde el lado del servidor podemos consultar.

Para gestionar cookies en Flask tenemos que afrontarlo en dos pasos. En primer lugar tenemos que establecer el contenido de la Cookie y crearla y en un segundo paso recuperar del servidor el valor de esa cookie.

Guardar una Cookie


Para guardar una cookie tenemos que tener presente, que la información que queremos guardar debe viajar y guardarse en el cliente, por lo tanto debemos devolverla en una respuesta, por lo que vamos a utilizar el objeto respuesta para devolver esa información.

Para crear el objeto respuesta utilizaremos el método make_reponse().

 respuesta = make_response('Hemos guardado la cookie')

A este método make_response() le podemos pasar:

  • Una página
respuesta = make_response(redirect('/'))

Depués de guardar la cookie nos devuelve, en este caso, a la página inicial. 

  • Un contenido en texto
respuesta = make_response('Hemos guardado la cookie')

Guarda la cookie y nos muestra un texto en pantalla. 

  • Json.


Por ser lo más sencillo vamos a pasarle un texto ('Hemos guardado la Cookie'). 

Ahora sobre respuesta aplicaremos la cookie  con el método set_cookie()

respuesta.set_cookie(Key="nombre de la cookie", value="valor a guardar", secure = True)

Pongo el argumento "secure = True" ya que sino, FireFox y otros navegadores aunque no lo muestren directamente,  en opciones del desarrollador nos da la siguiente alerta:

"Cookie “galleta” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, without the “secure” attribute. To know more about the “SameSite“ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite"

Esto también lo podríamos haber hecho usando las opciones de configuración de Flask con:

SESSION_COOKIE_SECURE = True


Bien, ya solo nos quedará devolver el objeto respuesta como return de la petición.

La aplicación de flask podría quedar de esta forma:

from flask import Flask, make_response
app = Flask(__name__)


@app.route('/')
def home():
    return 'Esta es la pagina inicial'


@app.route('/set_cookie/')
def save_cookie():
    respuesta = make_response('Hemos guardado la cookie')
    respuesta.set_cookie('mi_cookie', 'esto se guarda en la cookie', secure = True)
    return respuesta

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

y si la ejecutamos:


Para ver la cookie podemos ir a herramientas del desarrollador web y dentro de la consola veremos nuestra cookie.




Otra opción es chequear la pestaña de almacenamiento dentro de las herramientas del desarrollador, y allí podrás ver la cookie:






Leer una Cookie.


Para recuperar una cookie del navegador, que no es más que una cadena de caracteres, utilizaremos el objeto request.cookies.get de la siguiente forma:

leer_cookie = request.cookies.get('nombre de la cookie')

Nuestro archivo quedaría de la siguiente forma:

from flask import Flask, make_response, request
app = Flask(__name__)


@app.route('/')
def home():
    return 'Esta es la pagina inicial'


@app.route('/set_cookie/')
def save_cookie():
    respuesta = make_response('Hemos guardado la cookie')
    respuesta.set_cookie('mi_cookie', 'esto se guarda en la cookie')
    return respuesta


@app.route('/get_cookie/')
def read_cookie():
    leer_cookie = request.cookies.get('mi_cookie')
    return f'Contenido de la cookie -> {leer_cookie}'


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

y al ejecutarse:


Las Cookies pueden expirar: Los argumentos max_age y expires.


Por defecto, las cookies expiran cuando el usuario cierra la sesión, esto es en cuanto se cierra el navegador. Para conservar una cookie podemos utilizar los atributos expires o max_age.

Cuando se usan ambos max_age tiene preferencia sobre expires. 

En Lugar de expirar cuando se cierra el navegador, las cookies permanentes expiran en una fecha especifica (expires) o tras un periodo de tiempo especifico en segundos (max_age).

Nota: Cuando se establece una fecha de expiración, la fecha y hora que se establece es relativa al cliente en el que se establece la cookie, no del servidor.

Por ejemplo para poner una cookie que se conserve durante 5 minutos podemos usar la siguiente vista:

Cookie con una duración de 5 minutos.

@app.route('/set_cookie5m/')
def save_cookie5m():
    # make_response = La respuesta será 
    respuesta=make_response('Hemos guardado la cookie')
    # en respuesta ira la cookie 'nombre' 'la cookie en si'
    # La duración es max_age es 5 minutos lo que en segundos es 60 s * 5
    respuesta.set_cookie('galleta','Estoy guardado en el navegador', secure=True, max_age=60*5)
    return respuesta

si queremos borrar la cookie ponemos max_age = 0.

Un ejemplo de como establecer una cookie valida durante 90 días con el atributo expires. 

expires => Debe ser un objeto de datetime o la fecha puesta en formato Unix

Cookie con una duración de 90 días, usando expires y un objeto datetime.

@app.route('/set_cookie90d/')
def save_cookie90d():
    '''Pone una cookie en vigor durante 90 dias'''
    # make_response = La respuesta será 
    respuesta=make_response('Hemos guardado la cookie')
    # en respuesta ira la cookie 'nombre' 'la cookie en si'
    # expire = es un objeto de datetime().
    final = datetime.datetime.now()
    final = final + datetime.timedelta(days=90)
    respuesta.set_cookie('galleta','Estoy guardado en el navegador', secure=True, expires=final)
    return respuesta


Las Cookies están limitadas al directorio. El atributo path.


Si establecemos una cookie con el atributo path, esta queda asociada a esa determinada ruta. Una cookie con un atributo de ruta determinado no se puede enviar a una ruta con la que no esta relacionada, incluso si ambas rutas conviven en el mismo dominio.

Cuando se omite el atributo path durante la creación de la ruta, esta se aplica a todo el dominio "/".

Por ejemplo el código:

respuesta.set_cookie('galleta','Estoy guardado en el navegador', secure=True, 
    path="/get_cookie/")

hace que la cookie quede asociada a esa ruta y no se pueda leer desde ninguna otra.

También las cookies están asignadas a un determinado dominio salvo que se especifique otra cosa con el atributo domain.


¡No toques mi cookie! El atributo HttpOnly.


El atributo HttpOnly al establecer una cookie garantiza que un código escrito en JavaScript no pueda acceder a las cookies. Esta es la forma mas importante de protección contra ataques XSS

Para marcar una cookie como HttpOnly en flask usaremos el siguiente  atributo:

respuesta.set_cookie(key="<nombre>", value="<valor>", httponly=True)


Una cookie marcada como httponly no puede ser accedida desde JavaScript: si utilizamos el inspector de consola del navegador, en documento.cookie aparecerá una cadena de texto vacía.

¿Cuando usar HttpOnly? Pues siempre que puedas. Las cookies deben ser httponly, a menos que tengas un buen motivo para que ser tenga que acceder a ellas usando Javascript en tiempo de ejecución.









miércoles, 2 de septiembre de 2020

Flask 16. Mostrando mensajes en pantalla con el método Flash de Flask.

Fotos preguntas libres de regalías | Pxfuel


Anteriormente. Flask 15.Construyendo un Login de usuarios con Flask-login, Flask-WTForms, Flask-SQLALchemy y Flask-Bootstrap


Flash (con h no con k)

Es bueno que las aplicaciones y las interfaces de usuario reciban comentarios a las acciones que realicen los usuarios en ellas (feedback en inglés). 

Por ejemplo, imagina que utilizas el código del post anterior, que era para crear un sistema de login de usuarios. En el había un formulario para registrarse y otro para poder luego iniciar sesión. Imaginemos que el usuario ya se ha registrado pero que a la hora de loguearse no introduce bien la contraseña. 

Nos parece natural que en algún lugar de la pantalla aparezca un mensaje avisando de este hecho. Si no apareciese nada, el que lo esta utilizando no sabría si ha metido mal el nombre, el usuario, la contraseña o que sencillamente la aplicación no funciona.

En el post anterior esto lo solucionamos devolviendo una nueva página en la que si el nombre del usuario no existía en la base de datos, o bien existiendo,   la contraseña era incorrecta, nos mostraba un texto  que decía:



Pero estaría aún mejor, si en el propio formulario de login nos avisará de este error de esta forma:


Pues este mensaje y otros los podemos mostrar de una forma muy sencilla usando el método flash de Flask.

Utilizaremos el código del post anterior, del capitulo flask 15.

Partimos del archivo inicio.py. Como vamos a utilizar el método flash lo importaremos al principio del programa junto con todos los otros métodos que utilizamos de la librería de Flask.

from flask import Flask, render_template, redirect, url_for, request, flash


Después en la vista login



vamos a cambiar el código dentro del rectángulo verde por este otro, para que se renderice el mensaje en la página del formulario directamente si el usuario o la contraseña es errónea:


        # mensaje a mostrar
        flash('El usuario no existe o la contraseña es incorrecta')
        # vista en la que se mostrará
        return redirect(url_for('login'))


Ahora en tenemos que incluir en la plantilla login.html, el código necesario para poder mostrar el mensaje. Lo más práctico es poner este código en un archivo aparte, para luego poder importarlo en la plantilla en la que lo necesitemos. No obstante también podrías poner directamente el código dentro del propio archivo en la zona donde quieras que aparezca. Yo utilizaré en este ejemplo el primer método.

Aquí quiero hacer un inciso general. Lo más cómodo sería colocar este código en una plantilla base de la que hereden todas las demás plantillas (como puede ser base.html). De esta forma tenemos una especie de rotulo, que estaría disponible en todas las plantillas y que muestre los mensajes que queramos mandar sin tener que escribir el código en cada una de ellas (lo que tampoco es complicado como veremos ahora). No obstante en este ejemplo vamos a utilizar otra opción.

Proseguimos. Ahora, creamos un archivo llamado _flash.html dentro de la carpeta "templates". El poner el _ delante del nombre es para saber que este archivo va a usarse dentro de otro, va a ser incluido en otra u otras plantillas. Este es el código con la estética más sencilla. Solo mostrará el texto que hemos preparado cuando se lance el evento. Sin embargo también se pueden hacer cosas mas complicadas con algo de css (texto que parpadea, se mueve etc)

Directorio de Trabajo
    |_ /static
    |_     |_ mystyle.css  
    |_ /templates
    |       |_ pagina_inicial.html 
    |       |_ signup.html 
    |       |_ login.html
    |       |_ _flash.html
    |_ /base_de_datos
    |_      |_ dbase.db
    |_ form.py 
    |_ inicio.py                                                                             
    |_ config.py           


<!-- Para mostrar un mensaje en una vista con flash''' -->

{% with mensajes = get_flashed_messages() %}
    {% if mensajes %}
        {% for mensaje in mensajes %}
            {{ mensaje }}
        {% endfor %}
    {% endif %}
{% endwith %}


Cuando llamemos a la función flash() dentro de la vista login, Flask guardará el mensaje, pero flashear el mensaje no hará que aparezca por arte de magia en las páginas web. La plantilla de la aplicación necesita "renderizar" este mensaje que hemos flasheado de forma que se funcione y se acople al diseño del sitio. Por eso tenemos que añadir este código en la plantilla que queramos que lo muestre.

Estamos usando el constructor with para asignar el resultado de la llamada a la función get_flashed_messages() a la variable que hemos llamado "mensajes", todo dentro del contexto de la plantilla. La función get_flashed_messages() hace una consulta a la aplicación de Flask y devuelve una lista con todos los mensajes que se hayan "flasheado" previamente. El condicional que sigue comprueba si la variable mensajes tiene algún contenido, y en este caso, se renderizará cada mensaje de la lista. 

Por contra, si es la primera vez que se entra en la plantilla o tanto el usuario como la contraseña del formulario de login son correctos el evento no se lanzará (no se ejecutará la sentencia) y por lo tanto la variable "mensajes" estará vacía y no se mostrará nada.

Una propiedad interesante de estos mensajes flash, al utilizar el constructor with, es que una vez que se solicitan los mensajes a través de la función get_flashed_messages(), estos se eliminan de la lista de mensajes, por lo que aparecen solo una vez después de llamar a la función flash.

Para incluir este código en la plantilla lo haremos con:

    {% include "_flash.html" %}


y como quiero que el mensaje aparezca después del botón de envío del formulario y antes de la frase "No tienes cuenta. Regístrate" lo coloco en login.html, en esta línea del código:





Ahora ya solo queda ejecutar el programa y ver que funciona.


Código del Capítulo


Próximo episodio. Manejo de Cookies en Flask.