API REST com Flask e autenticação JWT

Hedgar Bezerra
19 min readJun 22, 2019

--

Fala ai pessoal! Bora ver como se faz uma API usando o micro framework Flask e algumas bibliotecas bem maneiras? Juro que não vou consumir muito do seu tempo e vai ser bem de boa! E no fim vou disponibilizar o código no GitHub pode ficar tranquilo.

Requisitos para o projeto:

  • Conhecimentos básicos com Python;
  • Conhecimentos básicos com HTTP;
  • Conhecimentos com banco de dados relacionais;
  • MySQL instalado na sua máquina;
  • PyCharm ou Visual Studio Code(ou qualquer outro editor que preferir);
  • Postman, Insomnia, ou se for raiz mesmo, usa biblioteca Requests do Python;
  • Muita vontade de aprender!

Bora codar!

Uma vez que o Flask não possui um padrão de arquitetura de projetos, acho interessante usarmos o padrão do Django que é outro framework Python bem maneiro.

1 — Iniciar o projeto

1- Vamos começar iniciando um projeto Python puro no PyCharm e criar um ambiente virtual(venv) que irá conter as bibliotecas utilizadas no projeto e assim não sujar nossa instalação principal do Python.

Após criar um novo projeto vamos começar criando uma pasta(package) chamada app que irá conter todos arquivos do projeto com exceção das configurações.

2- Podemos ver que dentro da pasta app foi criado um arquivo chamado __init__.py, dentro dele iremos criar a instância da nossa aplicação Flask mas antes disso devemos instalar o pacote do Flask na nossa venv, para isso usamos o comando pip install Flask na linha de comando do PyCharm ou indo nas configurações do interpretador e clicar no sinal de mais para adicionar um novo pacote.

3- Após termos instalado o Flask já podemos usá-lo, então voltando no nosso arquivo __init__.py vamos criar a instância da seguinte forma:

Com isso nós já conseguimos subir o servidor da nossa aplicação e definir todas configurações do nosso aplicativo, mas como comentado anteriormente vamos manter o código mais simples possível, sendo assim bora criar dois arquivos, um chamado run.py e outro chamado config.py eles sendo respectivamente responsáveis por subir nossa aplicação e pelas configurações.

4- Arquivo run.py ficará da seguinte forma:

Caso exista necessidade, você pode passar uma porta especifica para o nosso app ao parâmetro port, mas por padrão usaremos a 5000.

5- Arquivo config.py é onde passaremos todas as configurações da aplicação, onde eventualmente iremos definir nossa chave da aplicação e a conexão com banco de dados. Inicialmente adicionaremos apenas a opção de DEBUG e a definiremos como True, isso fará com que o servidor note alterações e reinicie automaticamente. Agora precisamos passar essas configurações para nossa aplicação, então bora voltar para o arquivo __init__.py na pasta app e deixa-lo da seguinte forma:

Com isso nossa aplicação irá ler todas configurações desse nosso arquivo config, bem fácil, né?

2 — Rotas

1- Já temos nossa aplicação de pé, temos algumas configurações bem básicas, bora criar uma rota!

O jeito mais simples de fazer isso é no nosso __init__.py, então bora lá testar isso. Para criar uma rota nós só precisamos da instância da nossa aplicação(Não crie uma nova, só importe da pasta app), mas nesse caso como exemplo vamos criar uma rota que irá devolver um JSON bem simples.

2- Com isso podemos dizer que fizemos uma API, você pode agora usar seu Postman para testar esse endpoint.

Podemos ver um retorno da nossa API com um JSON como definimos na nossa rota. Mas vamos nos aprofundar mais um pouco.

3- Dentro da pasta app, vamos criar outra pasta chamada routes, nela vamos ter um arquivo chamado routes que irá conter todas rotas, sem nenhuma lógica da API. Aqui temos nosso arquivo routes.py:

Caso tente acessar novamente a rota que acessamos a pouco no Postman, não vamos conseguir pois não existe nenhuma referência à este arquivo, então bora fazer isso no nosso __init__.py da pasta app.

Adicionando uma única linha podemos ter acesso à todas essas rotas com facilidade e mantendo nosso código bem limpo e estruturado.Por enquanto nossa estrutura do projeto está assim:

Bora para próxima etapa que ainda não terminamos!

3 — Conexão com banco de dados

1- Antes de começar a criar nossos models do banco de dados e views(também conhecidos como controllers em outras linguagens) precisamos baixar algumas dependências, então bora na aba de configurações do interpretador e instalar as seguintes bibliotecas:

  • Flask-SQLAlchemy;
  • marshmallow-sqlalchemy;
  • flask_marshmallow;
  • marshmallow(caso não seja instalado com flask_marshmallow);
  • mysqlclient.

2- Após instaladas essas dependências, bora passar algumas variáveis para nosso config.py que serão a nossa string de conexão com banco de dados(julgando que já tenha instalado)

Esta é minha string de conexão, o primeiro argumento será sempre o banco de dados que estamos usando como estamos usando o MySQL iremos passa-lo como argumento, após isto temos o root que representa o usuário do banco seguido por dois pontos vêm a senha que em questão não existe(Não façam isso em casa! É sério), por fim temos o host, se for banco de dados local, pode manter assim como o argumento seguido que é a porta, o MySQL usa a porta 3306 por padrão, no fim temos o banco/ schema que iremos utilizar(este deve ser criado manualmente).

3- Agora é só criar uma instância do SQLAlchemy que será a biblioteca responsável por cada iteração com banco de dados e por fim uma instância do Marshmallow que irá possibilitar a conversão das informações do banco para JSON de forma bem fácil.

Pode falar, foi bem de boa, né? Calma que já vêm mais!

4 — Criando model

1- Vamos criar nosso primeiro model que será users, para isso vamos criar uma pasta dentro da pasta app com nome models e dentro dela criar um arquivo users.py;

2- Primeira coisa a fazer no arquivo users.py é importar tanta instância do banco de dados(SQLAlchemy) quanto do Marshmallow assim como fizemos no routes.py com o app.

3- Após isso podemos já definir nosso model, para isso criamos uma classe que irá herdar do SQLAlchemy a classe Model e a partir dessa mesma variável podemos definir colunas.

Como podemos notar, a partir da instância criada no arquivo __init__.py da variável db que contém o SQLAlchemy podemos usar para criar as colunas e passar seus tipo de dados de acordo com o banco de dados, essa é a forma mais simples de se trabalhar usando SQLAlchemy com Flask. É possível passar argumentos para os campos das tabelas de forma bem fácil e até mesmo passar valores padrão com a utilização do parâmetro default.

4- Agora que temos nosso model criado podemos usar o Marshmallow para fazer a serialização, neste caso devemos usar o mesmo arquivo. Nós usamos a classe Users e passaremos seus campos para o Marshmallow para que ele possa criar JSON e assim mandar isso para o usuário.

Os Schemas do Marshmallow não precisam de uma classe base caso você vá utiliza-los sozinhos, porém como neste caso estamos usando junto ao SQLAlchemy devemos usar class Meta, que irá utilizar os campos da classe criada acima. Podemos ver também duas variáveis, serão elas que usaremos para dar retorno com JSON, o parâmetro many indica que será retornado um array.

5- Referenciar o modelo no __init__.py assim como fizemos com o arquivo routes.py:

Agora que temos o model criado e ele está referenciado, devemos ir a linha de comando do Python pelo próprio PyCharm, ou apenas abrir o CMD e navegar até nossa pasta, após isso é só dar os seguintes comandos:

Com isso a tabela de usuários foi criada, caso ocorra algum erro provavelmente é devido ao client do mysql que você provavelmente esqueceu!

Calma, se as coisas não fizeram sentido até agora, elas vão! Então bora fazer nosso CRUD.

5 — Fazendo as operações de um CRUD

1- Para começar a fazer nossa lógica, vamos criar uma pasta chamada views dentro da pasta app, nela vamos ter toda lógica e conexão com banco de dados, após criarmos a estruturado projeto deve ficar dessa forma:

2- Agora basta criar um arquivo users.py para podermos fazer nossas operações.

3- A primeira coisa que iremos fazer no arquivo users.py será importar o nosso banco de dados do app para que possamos fazer nosso CRUD, vamos precisar também importar nosso model que criamos nos passos anteriores e por fim, vamos importar request e jsonify do pacote flask. Como bônus podemos importar o Werkzeug security que é uma biblioteca que vem junto ao Flask e é bem interessante, vamos usá-la para fazer hash da nossa senha(Salvar senha como texto/plain text não é uma prática interessante). Deve ficar mais ou menos assim:

Já temos todas ferramentas que vamos utilizar para fazer o CRUD no nosso arquivo, agora é só colocar a mão na massa!

4- Vamos começar inserindo um usuário no banco de dados, para isso vamos criar uma função para isso, como estamos trabalhando com requisições por meio de JSON vamos usar o objeto que importamos anteriormente, o request. Esse objeto tem diversos atributos como: args, data, json, entre outros… Nós iremos utilizar o json para captar determinada informação dessa requisição, nós podemos simplesmente usar o data, assim captando toda informação do JSON, mas como vamos criar uma instância do nosso model Users a devemos captá-los de forma separada, através de colchetes.

Com isso nós já temos as informações enviadas pelo usuário(ou o nosso Postman) na nossa API.

5- Agora precisamos instanciar um objeto Users com essas informações, mas antes vamos usar o Werkzeug security para fazer o hash da nossa senha:

Bem simples né? Agora precisamos inserir essa informação no banco de dados e persisti-la.

6- Vamos fazer essa parte dentro de um try/except para o caso de ocorrer algum erro durante a operação, nós podermos retornar um JSON diferente para nosso usuário. No try iremos usar a variável db que importamos lá do nosso app e utilizaremos as sessões(Elas são basicamente a conexão com o banco de dados de fato e também podem ser consideradas uma “sala de espera” e por isso precisamos comitar as informações após inseri-las).

Como podemos notar, usamos uma sessão do nosso banco de dados para inserir um usuário e por fim devolvemos essas informações como JSON para nosso usuário usando o jsonify do Flask. Mas não devemos ignorar a utilização do Marshmallow que está entrando em cena agora. Estamos utilizando o objeto user_schema para transformar a nossa instância de Users em um objeto JSON e passá-lo de volta no JSON.

7- Agora para podermos utilizar essa função, devemos voltar às nossas rotas no arquivo routes.py e criar uma nova rota com método POST e chamar essa função, vai ser bem fácil, bora lá.

É bem fácil né? E o código continua bem pequeno e fácil de entender, essa é a importância de aplicar os princípios SOLID. Agora se tentarmos uma requisição nesse endpoint com o Postman, tudo deverá funcionar.

Sucesso!!

Agora nós podemos dar mais um passo(e aproveitar esse código) para podermos atualizar nossos usuários, então vamos lá!

8- Como dito anteriormente, vamos aproveitar boa parte do código…na verdade, ele todo(hehe). No nosso users da pasta view, vamos copiar a função post_user e renomea-la para update_user e adicionar um parâmetro chamado id, vamos precisar adicionar algumas coisas, primeiro vamos precisar procurar se existe um usuário com esse id, para isso usamos a função query do nosso model e por fim usamos outra função chamada get que faz consultas com base no id e apenas nele. Caso existe esse usuário nós iremos passar os dados da requisição para esse usuário e salvá-lo no banco, mas caso ele não exista, devemos retornar uma mensagem para nosso usuário, bora ver como ficaria isso:

Aqui estamos criando uma variável chamada user que irá receber um objeto do nosso model Users a partir da query e caso não exista um usuário com determinado id, nós retornaremos uma mensagem, caso ele exista iremos atribuir novas informações recebidas do JSON para esse usuário existente e então iremos comitar essas alterações, é bem simples mesmo!

9- Agora precisamos fazer a chamada dessa função em um novo endpoint que será com método PUT ele será bem semelhante ao POST com uma pequena diferença, esse endpoint irá receber um parâmetro e para isso usamos <> para passar valores às funções dos endpoints, bora lá!

Bem simples, agora já podemos utilizar esse endpoint, vamos para o Postman!

Mudamos todas as informações do nosso usuário apenas adicionando algumas linhas de código do primeiro código que criamos antes, agora seria interessante mostrar os usuários e um usuário específico, bora fazer nosso GET então!

10- Vamos voltar para nossa view chamada users e criar uma função chamada get_users para listar todos nossos usuários, esse será certamente o mais fácil até agora. Nessa função iremos criar uma variável que irá receber o valor de uma query bem semelhante ao que fizemos acima ao procurar se um usuário existia, mas nesse caso, vamos pegar TODOS!

Assim como nas outras funções devemos retornar determinado JSON caso existam ou não usuários, podemos notar que diferente das funções que criamos anteriormente, nossa variável result está recebendo users_schema ao invés de user_schema isso é porque o Marshmallow nos ajuda nessa parte ao retornar um array com todos os usuários e já transforma isso em JSON como havia comentado lá no inicio.

11- Para usarmos essa função, vamos voltar no arquivo routes.py e chamar essa função dentro de uma rota GET que irá retornar todas essas informações, faz assim:

Após feito isso, bora pro Postman testar esse endpoint!

Aqui temos uma lista com todos usuários criados, tudo isso bem fácil e organizado, agora vamos para próxima etapa.

12- Agora vamos retornar um usuário específico, na verdade, nós já fizemos isso quando fizemos atualização das informações porém não retornamos para o usuário essa informação, vamos fazer isso agora na nossa view users.py. Vamos começar criando uma função chamada get_user e ela receberá um argumento chamado id, agora iremos criar uma variável chamada user e ela receberá o valor de uma query como já fizemos a única diferença é que iremos usar o objeto do Marshmallow user_schema para transforma-la em JSON e por fim iremos retornar para o usuário se existe algo com o id recebido ou não.

Agora que temos a função precisamos criar um endpoint para recebe-la, voltaremos no arquivo routes.py e criaremos uma rota GET que receba argumentos como fizemos com o PUT, bora lá:

Vamos testar para ter certeza que tudo está de acordo.

E bem, está tudo certo sim(ufa..).

Com isso, nossa API está quase pronta. Vamos para o último verbo HTTP que veremos aqui, o DELETE.

13- Bem parecido com a função de atualizar os usuários, a de deleção funciona quando procuramos um usuário por seu id que virá da requisição, uma vez que tenhamos um usuário podemos fazer uso da nossa variável db para criar uma sessão e assim excluir essa informação do banco de dados, mas não podemos que as sessões não possuem estado, então precisamos comitar essas alterações e caso nosso try seja executado com sucesso, iremos retornar isso para o usuário, caso contrário ou o usuário não exista iremos retornar outras informações.

Agora já estamos mais acostumados então você já deve saber o que fazer agora…mas só pra garantir, bora voltar para nosso arquivo de rotas, o routes.py e criar uma rota para essa função com o método DELETE, não podemos esquecer de também pegarmos o id do endpoint!

Agora só nos resta testar com o Postman, o resultado final deve ficar assim caso tudo ocorra com sucesso:

Ou assim, se o id que passamos não esteja relacionado com nenhum usuário no banco:

Com isso nós finalizamos o CRUD da nossa API, próximo passo vai ser a autenticação por meio de token.

6— JSON Web Tokens

Agora para finalizar vamos fazer algo bem interessante que é a autenticação por tokens, o que iremos fazer basicamente consiste em gerar um hash que será autenticado nos servidores e esse hash irá conter o username do usuário assim como a SECRET_KEY da API. Para utilizar o JWT no Python nós podemos utilizar diversas bibliotecas, mas no nosso caso iremos utilizar PyJWT.

1- O primeiro passo vai ser criar uma chave de segurança para nossa aplicação, para isso vamos para nosso arquivo de configuração, o config.py nós podemos inserir qualquer string como nossa chave de segurança, mas vamos fazer algo legal, pelo bem da prática, e vamos gerar uma chave aleatória com números e letras. Para isso precisamos importar random e string.

2- Vamos começar definindo uma variável chamada random_str ela irá conter uma string que será “escolhida” aleatoriamente usando a função choice da classe random e por fim, iremos atribuir essa string gerada para nossa SECRET_KEY, bora visualizar isso:

Como podemos ver a random_str está recebendo diversos atributos do arquivo importado string, até dado momento eles não possuem valor algum eles são apenas uma representação. Mas quando estamos atribuindo a nossa variável key, estamos juntando os valores dessa string que estão sendo escolhidos aleatoriamente com a função choice através de um loop(o valor 12 representa o tamanho da nossa key). Com isso já podemos ir para o próximo passo.

3- Agora precisamos fazer a instalação da biblioteca PyJWT, existem muitas parecidas mas o nome é esse, caso instale outra, talvez sequer funcione. Então é ideal instalar através do gerenciados do PyCharm para facilitar sua vida.

4- Após ter instalado já podemos colocar a mão na massa, então vamos criar um arquivo chamado helper.py na pasta de views. Neste arquivo iremos fazer toda parte de autenticação então vamos começar importando algumas coisas: Vamos começar importando nosso app, o jwt(sim, sem o py na frente), o werkzeug.security como fizemos no arquivo users.py, vamos importar request, jsonify do flask e por fim wraps do functools para podemos criar decorators que serão usados para alterar a forma como algumas funções agem, são ferramentas bem úteis, mas não entraremos em detalhes.

5- Antes de começarmos seria interessante irmos a nossa view users.py e criarmos uma função que irá nos ajudar muito nessa parte de autenticação, essa função irá fazer uma query nos usuários com determinado username. Podemos fazer isso diretamente no helper.py mas isso facilitará bastante nosso trabalho, então vamos lá: Vamos chamar essa função de user_by_username, ela deverá receber um parâmetro chamado username, ou o que preferir. vamos usar try/except para o caso de erro, ou não existir um usuário. Até o momento para encontrar usuários utilizamos o get, neste caso usaremos o filter que nos permite utilizar os campos do nosso model Users para filtrar as informações, nossa função ficará bem assim:

Bem simples e vai nos ajudar um bocado! Agora sim podemos ir para o arquivo helper.py

6- Agora no arquivo helper.py vamos começar criando uma função chamada auth, usaremos ela para autenticar o usuário no sistema e por fim gerar um token que será retornado para ele. Vamos começar criando uma variável chamada auth, ou autenticacao se preferir, ela irá receber um header com basic authorization do objeto requests do Flask e já aproveitando, iremos validar se essa variável não está vazia e já retornar uma resposta adequada:

Aqui já temos uma validação básica da request, agora precisamos validar o usuário no nosso banco de dados.

7- Para fazer isso devemos importar aquela função que acabamos de criar no users.py para pesquisar usuários pelo nome, então iremos criar uma variável chamada user que irá receber uma instância do nosso model Users da função user_by_username, como argumento iremos passar o username da variável auth, ela possuí usuário e senha, é a autenticação básica. E iremos validar caso esse usuário não exista iremos para retornar outra mensagem:

Nossa função está assim até o momento, até o momento validamos se existe um cabeçalho de autenticação e se o usuário existe no banco, precisamos agora verificar se as senhas coincidem, mas como fizemos hash usando o werkzeug.security devemos usá-lo também para validar essa senha.

8- Para validar essa senha, o werkzeug provê uma função chamada check_password_hash, nós iremos validar a senha do cabeçalho de autenticação, ou seja, da nossa variável auth e a senha do banco que vêm da nossa variável user. Uma vez que essa senha for validada iremos criar nosso token usando o jwt.encode que irá receber um dicionário com opções como o payload, o método de hash, a nossa SECRET_KEY(Esse é o momento dela brilhar!) e também um tempo de expiração e por fim retornar para o usuário. No caso da validação falhar devemos também dar um retorno para que ele autentique novamente seu usuário e senha.

Isso é o que precisamos para fazer a autenticação com jwt, podemos criar um endpoint para receber essa função.

9- Vamos para nosso arquivo routes.py e criar uma rota de autenticação ela vai ser do tipo POST, vamos chamá-la de auth, mas não esqueça de importar o arquivo helper.py caso contrário, nem vai funcionar.

Agora só precisamos testar, mas é importante lembrar de colocar o username e password na aba de authorization do Postman .

Se foi tudo certinho, inclusive a senha e usuário é claro, essa deve ser a mensagem de resposta da API, mas no caso de erro de autenticação essa será a resposta:

Neste momento já concluímos 90% do nosso projeto, agora precisamos fazer com que nossos endpoints entendam que uma verificação é necessária e para isso iremos usar decorators.

10- Vamos começar criando uma função chamada token_required no nosso helper.py, ela será um decorator e será utilizada para checar se foi passado um token para um endpoint, então ela irá receber uma função e para transformar uma função em decorator devemos usar o wraps que acabamos de importar, usaremos o wraps como decorator de uma segunda função que irá receber qualquer e todo argumento, bora visualizar isso:

Claro que ainda iremos implementar uma lógica, esse é o motivo do erro.

11- Agora precisamos criar uma variável token que irá receber nosso objeto request do Flask, até agora nós estivemos pegando as informações das requisições utilizando json, mas neste caso iremos utilizar o args.get pois precisamos pegar o token do cabeçalho da requisição ou da própria URL. Caso este token não exista não podemos autenticar esse usuário, né? Então devemos retornar uma mensagem para ele, mas caso ele exista devemos validar esse token usando o jwt.decode que fará o oposto do que fizemos anteriormente com o encode. Bora ver como ficaria essa parada:

Com isso nós temos um token(ou não né rs), agora o que precisamos fazer é usar o decode para pegar as informações desse token caso ele seja válido.

12- A partir desse token é possível acessar informações que foram passadas para ele, no nosso caso o que passamos foi o username do usuário e a SECRET_KEY da API. Para usar o decode nós iremos usar o nosso token como parâmetro e a SECRET_KEY para validar se esse token foi gerado dessa aplicação de fato. Uma vez que ele for validado iremos criar uma variável chamada current_user, ela irá efetuar uma pesquisa no banco de dados a partir da nossa função na view users.py, user_by_username e nós iremos retornar esse usuário junto aos argumentos do decorator.

O motivo pelo qual retornamos o usuário que está usando esse token, é para futuras relações com outras tabelas, ou mesmo para manipulação do mesmo através da API. Agora que nosso decorator que faz a validação está feito, nós iremos inseri-lo em uma rota, em bora para o routes.py.

13- Para usar um decorator é bem simples, na verdade, estivemos usando um desde o começo o @app.route para utilizar o decorator que acabamos de criar é só colocar @ e chamar nossa função, bora ver como fica:

E com isso já podemos utilizar nosso token nessa rota, caso queira testar em mais rotas é só chamar a função como decorator, vamos ver como fica no Postman.

Aqui já podemos ver a primeira parte da validação sendo efetuada, na falta de um token, será retornada uma mensagem para o usuário, agora tentaremos com um token inválido:

Por fim, iremos testar com um token recém gerado pela rota /auth e passá-lo na nossa URL:

Se quisermos tentar algo mais elaborado, podemos acessar os dados desse nosso current_user, como vou mostrar no exemplo abaixo:

E o resultado dessa request vai ser algo assim:

Bom pessoal, chegamos ao fim. É triste, eu sei…mas se chegamos até aqui conseguimos aprender bastante coisa(eu espero rs).

We did fellas!

Esse é todo conteúdo simples que consigo passar para vocês, mas sempre recomendo o pessoal ler documentação para poder se aprofundar em todas ferramentas que as bibliotecas possuem pois aqui não usamos um terço das possibilidades. Então se quiser aprender mais dê uma pesquisada na documentação das bibliotecas que usamos aqui, durante o artigo existem alguns links que achei que seriam uteis, também recomendo que os leiam!

Link do projeto no GitHub como prometido!

--

--