Utilizando JWT no Lumen — parte 3 (Incluindo QUOTA de acesso)

Utilizando JWT no Lumen — parte 3 (Incluindo QUOTA de acesso)

Fala pessoal!

Hoje vamos para mais um artigo sobre JWT no Lumen! Esse artigo em questão será um pouco mais complexo, já que iremos fazer algumas alterações grandes no nosso projeto. Então caso você tenha alguma dúvida ou não consiga executar alguma coisa, por favor, veja o repositório que estará ao final ou pode mandar mensagem aqui nos comentários

No final do post está o repositório do GIT com todo esse arquivo que foi feito passo a passo

Artigos anteriores

Caso você não esteja acompanhando os artigos, sugiro que você vá para o primeiro clicando no link abaixo

Utilizando JWT no Lumen — parte 1

Link do postman

Link das requisições do postman para importar no seu !

Postman

Considerações importantes

Iremos refazer algumas partes do nosso projeto da parte 2 para que possamos limitar o acesso demasiado à nossa API, pois até a parte 2 , se o cliente em questão logar e pegar o token, ele pode fazer infinitas requisições, e as vezes em alguma regra de negócio isso não é vantagem, pois normalmente quem é a dona da API tem alguma informação relevante para oferecer, e custa caro manter. Em vista disso, podemos limitar e incluir quantidades de acesso à nossa api que só serão consideradas no momento em que a resposta for OK (200), assim, nem o cliente e nem a empresa fornecedora sairão no prejuízo de gastar uma requisição e o servidor retornar alguma falha ou estar indisponível

Novas tabelas e novos models!

Para esse tipo de ação, nós iremos precisar criar 2 tabelas novas no nosso sistema. Uma de transações, para que possamos contabilizar quantas requisições o cliente fez e gravar qual serviço ele acessou. (isso é extremamente importante caso você queira fazer algum tipo de relatório de uso por exemplo)

Como é uma tabela nova iremos criar com o comando do artisan para criar

php artisan make:migration transactions --create=transactions

Essa migration contará com uma particularidade, iremos fazer uma chave estrangeira no banco de dados, associando o usuário à requisição. Os frameworks, principalmente o Laravel/Lumen são extremamente poderosos e ágeis para esse desenvolvimento, com somente 2 linhas de código no migration você faz uma referência a outra tabela

Schema::create('transactions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->string('service');
$table->timestamps();
});

Sempre que for fazer algum tipo de chave é obrigatório o uso do unsignedBigInteger, pois todos os id() são bigIncrements, que são unsignedBigInteger.
Não irei entrar no mérito de explicação de como fazer só demonstrando os campos que serão necessários para a criação da tabela

Depois disso precisamos criar uma tabela que iremos utilizar para colocar quotas extras ao usuário, pois ao fazer o cadastro (iremos mudar também a migration do usuário) ele receberá uma quantidade padrão de requisições e quando esse limite for excedido ele pode 'contratar' mais.
Então utilizaremos o comando

php artisan make:migration extra_quotas --create=extra_quotas

que também terá uma associação ao usuário, além da quantidade de requisições a mais o usuário vai ter.

Schema::create('extra_quotas', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->integer('quantity')->default(1);
$table->timestamps();
});

Model de Transações

Iremos criar um model com os campos de user_id e service, porém com uma diferença. O Lumen permite que criemos um método que vai retornar os dados da ligação da chave estrangeira de um jeito extremamente simples

Model de quota extra

O model de quota extra é bem simples, onde só iremos colocar qual usuário que está recebendo e qual a quantidade a mais de quotas irá receber, também com a referência do usuário ao user_id

Os 2 modelos criados com sucesso! Porém precisamos fazer mais uma modificação, teremos que incluir mais 2 campos na tabela de usuário:
1 — Precisamos criar um campo para que seja informado qual será a quota padrão do usuário, ou seja, quantas requisições ele pode fazer até ser bloqueado.

2 — Precisamos criar um campo para identificar o cliente admin, pois só ele por meio da API poderá incluir quota nos clientes.

php artisan make:migration change_user_quota_admin --table=users

Feito isso iremos incluir os 2 campos no método up

Schema::table('users', function (Blueprint $table) {
$table->tinyInteger('is_admin')->default(0)->after('password');
$table->bigInteger('quota')->default(100)->after('password');
});

Ao criar o usuário, por padrão ele tera uma quota de 100. porém não iremos fazer nesse artigo a alteração do método register para receber mais um campo no post chamado quota, mas se quiser pode fazer sem problemas. Além disso tem o is_admin que todos iniciam com 0 e somente alguns ou alguém terá acesso administrativo.

Ao finalizar a alteração nos migrations do token, quota e usuário você já pode executar o comando

php artisan migrate

Com isso todas as alterações na tabela usuário e as outras 2 tabelas serão criadas no seu banco de dados!

(EXTRA) — Criando um faker para ADMIN

Esse passo não é obrigatório, você pode registrar algum usuário na api, ir no seu mysql e alterar via SQL o is_admin de 0 pra 1 sem problema nenhum. Porém vamos criar via seed um usuário admin para facilitar a nossa vida e você acabar aprendendo mais uma função poderosa do framework

Vá no arquivo database/factories/UserFactory.php e amos modificar o arquivo UserFactory que está lá. Como é uma coisa simples iremos utilizar esse mesmo!
No método definition iremos criar dados fake para nossa aplicação e na minha opinião o Faker é uma das melhores coisas pro desenvolvedor.

Aqui estamos criando um usuário chamado superUser com o email superuser@superuser.com, e o faker está se encarregando de criar uma senha e 2 uuid para o client_id e o client_secret ( Você pode olhar lá no MailTrap.io que você vai ver este e-mail chegando também)

Já que estamos fazendo esta alteração, vamos fazer também mais algumas modificações

Removendo o generateKey do AuthController

Como o client_id e o client_secret são coisas geradas automaticamente e não dependem de nenhuma alteração do usuário, iremos criar mais um método de observer, que se chama creating. Diferente do created, o creating é chamado antes de inserir no banco de dados, portanto podemos separar nosso código e deixá-lo mais limpo.

Classe Commons

Vamos criar uma classe Commons para tirar o método generateKey de responsabilidade do AuthController e vamos passar para essa classe e mudar de nome, já que esse método somente gera um uuid, vamos manter o nome de uuid.

Crie uma pasta no mesmo nível da pasta app chamada Helpers, e dentro dela um arquivo chamado Commons.php. Pode ser que a gente sinta necessidade de criar alguns métodos comuns entre todas as classes então esse será nosso melhor lugar

Feito essa classe podemos ir lá no nosso AuthController.php, remover nosso método generateApiKey e também as associações dela no $user

//REMOVER ESSAS LINHAS DO SEU ARQUIVO AuthController.php,
$user->client_id = $this->generateApiKey();
$user->client_secret = $this->generateApiKey();

Feito isso iremos lá no UserObserver.php e criar o método estático creating e ali sim o observer irá se encarregar de gerar os uuids para os respectivos campos , vale lembrar que se você não estiver usando IDE com auto complete e etc você vai ter q dar um use no Helpers/Commons;

use AppHelpersCommons;
porém esse arquivo também estará disponível para consulta no repositório

static::creating(function($model){
$model->client_id = Commons::uuid();
$model->client_secret = Commons::uuid();
});

Feito isso podemos fazer um teste, limpando toda nossa base de dados e criando novamente, mas CUIDADO, esse comando não deve ser executado em produção sob hipótese alguma!

php artisan migrate:fresh
php artisan db:seed

Esse comando irá dar rebuild no seu banco de dados, dando drops e creates nas suas tabelas, fazendo com que seu banco de dados esteja limpo como se nunca estivesse sido utilizado e o db:seed irá criar o usuário Admin, aí é só ir no endpoint e criar qualquer outro usuário no /register

Criando o controller de Transaction

O controller Transaction vai ser responsável por incrementar uma transação, capturar a quantidade de transações que foram feitas em somatório ou detalhado do mês corrente, além de ter um método de build, que iremos transformar nossa resposta do serviço com uma informação de quantas requisições ainda faltam para exceder o limite , caso ele queira tomar alguma ação ou fazer algum tratamento no sistema. O controller está bem simples e de fácil entendimento então vou colar aqui sem muitas explicações dela.

Criando o controller de Quota

O controller de quota assim como o de Transaction terá um método para incrementar uma quota extra para o usuário (somente se o usuário que está ativando a API for admin), além de centralizar métodos estáticos de totalizadores também. Além disso existe um validate para que não insira nenhuma quota para usuários que não existem no sistema, assim com a diretiva exists: no validate, dizemos para o validador que para a quota ser adicionada o e-mail em questão deverá existir. Vamos fazer por e-mail, pois o client_id e client_secret deverá ser somente de posse do usuário e em momento algum deverá ser distribuído, portanto o e-mail se torna único porém livre de problemas para compartilhar. Outra coisa importante é que

Endpoints no web.php e alteração do método me

Iremos criar 2 rotas novas inicialmente

1 — Iremos criar a rota /me/detail, onde quando acionada iremos mostrar mais detalhes do usuário , como por exemplo a lista de serviços que foram acionadas no último mês

2 — Criar a rota /quota no verbo PUT para incrementar uma quantidade a mais de transações.

$router->get('/me/detail', 'AuthController@me');
$router->put('/quota', 'QuotaController@incrementQuota');

Essas duas rotas devem estar dentro do grupo que contém o middleware auth, pois essas rotas só poderão ser acessadas se o usuário estiver com o token.

Os possíveis retornos de json serão

No caso do /me

No caso do /me/detail, será um json mais detalhado

podemos ir lá no nosso model de transaction e colocar um array chamado $hidden onde podemos informar quais campo queremos esconder de um select, no caso podemos colocar o updated_at como oculto já que nesse caso sempre as datas serão iguais, então basta você incluir mais esse campo no seu array de hidden e pronto. Na sua consulta agora de 'detail' não virá mais o campo de updated_at

protected $hidden = ['id',  'user_id', 'updated_at'];

Feito isso, vamos lá na nossa AuthController e vamos alterar o método me para suportar além dos dados do usuário mostrar qual é a quota dele, quantas ele já usou e quantas restam, e quando for detalhado, mostrar a lista de serviços q ele utilizou no mês corrente, como é o último método que iremos fazer nesse controller, irei colocar o código completo dele

TA…. mas e ai como que eu uso essas QUOTAS?
Agora precisamos criar um controller de exemplo para consumirmos a api e fazermos o uso da quota em si. NESSE exemplo iremos criar um controller chamado company, somente para testes internos. Porém ao decorrer das partes iremos abordar um esquema de microserviços, quebrando essa responsabilidade da empresa para outro serviço, separando o JTW , QUOTA/TRANSACTION e os outros serviços subsequentes em pequenas partes independentes.

Criando o CompanyController

Vamos criar um controller chamado CompanyController na nossa pasta de controllers somente para consumir de teste nossa API e contabilizar o uso.

Iremos utilizar uma api muito conhecida, o viacep, que consiste em retornar somente um json de um cep específico.

Criando a roda para o cep

Vamos criar a rota do cep dentro da área que é necessário estar autenticado para utilizar, ou seja dentro do middleware 'auth'

$router->get('/cep/{cep}', 'CompanyController@cep');

Feito isso você já poderá acessar o cep via get no postman (que já está disponível com estas atualizações.

Se você olhar no método do CEP, nós só incrementaremos com +1 nas suas transações somente quando o servidor retornar alguma informação. e além disso iremos modificar o resultado da transação em um array mais trabalhado onde no retorno também informará quantas requisições ele ainda tem para utilizar, assim quem está chamando o serviço pode tomar qualquer tipo de atitude mediante esta informação

Acho que assim encerramos esta parte de JWT, criamos um serviço do ZERO, criamos 2 métodos de autenticação do usuário e definimos quantidade de quota para uso das apis pelos clientes.

Espero que tenham gostado e logo mais irei incrementar ainda mais esse serviço, separando enfim em pequenos serviços somente com a sua competência e nada mais e como fazer a comunicação entre eles.

REPOSITÓRIO DO GIT

brasizza/lumen-jtw-quota