---
# System prepended metadata

title: 19 Autenticação
tags: [web, day2]

---

---
tags: web,day2
robots: noindex, nofollow
lang: pt-br
---

# 19 Autenticação

O nosso blog atualmente tem um problema bem grande, qualquer usuário pode facilmente entrar na URL http://localhost:5000/new e adicionar um novo post mesmo sem a necessidade de autenticação.

O processo de autenticação funciona com os seguintes componentes:

- Uma base de dados com os usuários e suas credenciais devidamente criptografadas
- Uma view de login onde o usuário informa as credencias e a partir da verificação é feito o login
- Um registro na `session` (memória do servidor) informando que o usuário está autenticado
- Um cookie gravado no navegador contendo o ID da session

![](https://github.com/rochacbruno/python-web-api/raw/day2/docs/day2/imgs/session_auth.png)

Existem diversas tecnologias e metodologias para controle de autenticação mas a autenticação baseada em cookies e sessions ainda é a mais utilizada quando falamos de web sites.

> **NOTA** Em breve, quando falarmos sobre o FastAPI vamos utilizar outro modelo de autenticação usando Tokens.

Portanto vamos desenvolver a camada de autenticação da nossa aplicação Flask no arquivo `auth.py`


### Criando session no Flask

No Flask podemos criar novas sessions usando o objeto `session` e isso pode ser feito no contexto de `request` pois a session precisa ser criada e ter seu `ID` enviado de volta ao cliente que requisitou para ser armazenado como um cookie.

Como os dados da session são encriptados um requisito para usar o objeto `session`
é colocar uma variavél `SECRET_KEY` nas configurações e você pode usar qualquer string como sua secret key desde que garante que não vá perde-la.

portanto no `settings.toml` adicione 

```toml
secret_key = "ThisIsARandomSecretKey"
```

**NOTA**: é recomendado gerar uma secret_key mais complexa e dificil de ser descoberta, Python oferece uma função que ajuda a gerar

```bash
python -c "import secrets; print(secrets.token_hex())"
aeeda9006f2eea595516f113468ddbfc4e0ca170ad40a612c6fc30346f4fcffb
```

Agora podemos adicionar valores a session alterando o arquivo `views.py`

```python

from flask import (
    # ...
    session,  # NEW
)


@bp.route("/")
def index():
    posts = get_all_posts()
    session["test"] = "valor"   # NEW
    return render_template("index.html.j2", posts=posts)

```

Agora ao executar e efetuar uma requisição verá no navegador que junto do Response terá recebido um `cookie` com o ID da `session` armazenado

![](https://github.com/rochacbruno/python-web-api/raw/day2/docs/day2/imgs/session_inspect.png)

A partir de agora, a cada request que este navegador fizer para o mesmo servidor este cookie será enviado de volta e o servidor irá saber qual o ID da sessão que pertende ao usuário.

Do lado servidor armazenamos `{"test": "valor"}` mas do lado cliente o navegador só tem acesso a um ID `session:"eyJ0ZXN0IjoidmFsb3IifQ.Yrr-sg.oVwpMXQHyeX_jxSxcHYG3ohK5xo"` que só pode ser decriptado em um servidor que tenha a mesma SECRET_KEY que configuramos no `settings.toml`.


### Como fazer o processo de login?

Com o conhecimento da session o que podemos fazer é o seguinte:

1. Um formulario com os campos `username` e `password`
2. Uma view `/login` para onde o formulário será submetido
3. Uma função que irá comparar as credenciais informadas com as credenciais existentes no banco de dados de usuários
4. Em caso de sucesso a view `/login` armazena uma session `{"logado": True}` informando que o usuário efetuou o login com sucesso.


Podemos fazer isso tudo do zero, mas as coisas ai envolvidas são exatamente as mesmas que já vimos: view, URL, formulário, session.


### Flask Simple Login

Existem várias extensões que automatizam esse processo no Flask, entre elas está a Flask-Login e a Flask-SimpleLogin, aqui vamos usar a Flask-SimpleLogin por 2 motivos:

- Ela é mais simples
- Ela é compativel com MongoDB
- Eu que sou o criador da biblioteca :) 


Adicionaremos ao `setup.py`

```python
from setuptools import setup

setup(
    name="flask_blog",
    version="0.1.0",
    packages=["blog"],
    install_requires=[
        "flask",
        "flask-pymongo",
        "dynaconf",
        "flask-bootstrap",
        "mistune",
        "flask-simplelogin",  # NEW
    ],
)
```
e
```bash
pip install -e .
```

Agora vamos começar alterando o arquivo `auth.py` onde adicionaremos:

- Função para criar um novo usuário (com password encriptado)
- Comando para criar um novo usuário via CLI
- Função para verificar o login de um usuário
- Inicialização da lib Flask-SimpleLogin


`auth.py`
```python
import click
from blog.database import mongo
from werkzeug.security import check_password_hash, generate_password_hash
from flask_simplelogin import SimpleLogin


def create_user(**data):
    """Creates user with encrypted password"""
    if "username" not in data or "password" not in data:
        raise ValueError("username and password are required.")

    data["password"] = generate_password_hash(
        data.pop("password"), method="pbkdf2:sha256"
    )
    mongo.db.users.insert_one(data)
    return data


def validate_login(data):
    """Check that incoming credentials matches database stored credentials."""
    if "username" not in data or "password" not in data:
        raise ValueError("username and password are required.")

    db_user = mongo.db.users.find_one({"username": data["username"]})
    if db_user and check_password_hash(db_user["password"], data["password"]):
        return True

    return False


def configure(app):
    SimpleLogin(app, login_checker=validate_login)

    @app.cli.command()
    @click.argument("username")
    @click.password_option()
    def add_user(username, password):
        """Creates a new user"""
        user = create_user(username=username, password=password)
        click.echo("User created")

```

E então incluimos esse módulo no `settings.toml`

```toml
[default]
secret_key = "ThisIsARandomSecretKey"
mongo_uri = "mongodb://localhost:27017/blog"
title = "Meu Blog"
extensions = [
    "blog.database:configure",
    "blog.commands:configure",
    "flask_bootstrap:Bootstrap",
    "blog.views:configure",
    "blog.plugins:configure",
    "blog.auth:configure",  # NEW
]
```


Agora podemos rodar na linha de comando:

```bash
flask add-user --help
Usage: flask add-user [OPTIONS] USERNAME

  Creates a new user

Options:
  --password TEXT
  --help           Show this message and exit.
```

Para criar um novo usuário:

```bash
flask add-user admin 
Password: <admin>
Repeat for confirmation: <admin>
User created
```

Ao consultar no MOngoDB verá uma nova collection chamada `users` com um documento contendo nosso usuário `admin`

```json
{
  "_id": {
    "$oid": "62bb440ae895634ddcf4e236"
  },
  "username": "admin",
  "password": "pbkdf2:sha256:260000$sRqsgnNK5udc3UKs$eca1d3c73dbd177abdfd5942645007816ba066290cb64bd37107d600e95c969c"
}
```

E repare que o password está salco como um hash.

> **NOTA** existem outros algoritmos de hashing mais seguros que podemos usar no lugar do pbkdf2


### Protegendo o formulário com login


Agora sim temos usuário criado e a extensão simple login adicionada, basta dizermos ao Flask quais endpoints são protegidos e para isso vamos alterar o arquivo `views.py`

```python
from flask_simplelogin import login_required  # NEW

...

@bp.route("/new", methods=["GET", "POST"])
@login_required()  # NEW
def new():
    if request.method == "POST":
        title = request.form.get("title")
        content = request.form.get("content")
        slug = new_post(title, content)
        return redirect(url_for("post.detail", slug=slug))
    return render_template("form.html.j2")
```

Com o decorator `@login_required()` informamos que as credenciais devem ser verificadas para acessar a url `/new`

Execute a aplicação e acesse: https://localhost:5000/new 

![](https://github.com/rochacbruno/python-web-api/raw/day2/docs/day2/imgs/login.png)

Faça login com o usuário criado anteriormente `admin:admin` e verá com sucesso o formulário.

Para fazer logout basta acessar `/logout`.

> **DICA** Na documentação do Flask-SimpleLogin tem instruções de como customizar o template e mensagens da tela de login.
