Autenticação completa do usuário e controle de acesso – um tutorial do Laravel Passport, Pt. 1
Publicados: 2022-03-11Ao desenvolver um aplicativo da Web, geralmente é uma boa ideia dividi-lo em duas camadas. Uma API de camada intermediária interage com o banco de dados e uma camada da Web geralmente consiste em um SPA ou MPA de front-end. Dessa forma, um aplicativo da Web é mais flexível, facilitando o gerenciamento e a depuração a longo prazo.
Quando a API foi criada, configurar a autenticação e o estado em um contexto de API sem estado pode parecer um pouco problemático.
Neste artigo, veremos como implementar a autenticação completa do usuário e uma forma simples de controle de acesso em uma API usando Laravel e Passport. Você deve ter experiência em trabalhar com Laravel, pois este não é um tutorial introdutório.
Pré-requisitos de instalação:
- PHP 7+, MySQL e Apache (desenvolvedores que desejam instalar todos os três de uma vez podem usar o XAMPP.)
- Compositor
- Laravel 7
- Passaporte Laravel. Como as APIs geralmente não têm estado e não usam sessões, geralmente usamos tokens para manter o estado entre as solicitações. O Laravel usa a biblioteca Passport para implementar um servidor OAuth2 completo que podemos usar para autenticação em nossa API.
- Postman, cURL ou Insomnia para testar a API - isso depende da preferência pessoal
- Editor de texto de sua preferência
- Auxiliares do Laravel (para Laravel 6.0 e superior)—depois de instalar o Laravel e o Passport, basta executar:
composer require laravel/helpers
Com o acima instalado, estamos prontos para começar. Certifique-se de configurar sua conexão com o banco de dados editando o arquivo .env
.
Tutorial Laravel Passport, Etapa 1: Adicionar um controlador e modelo para solicitações fictícias
Primeiro, vamos criar um controlador e um modelo para solicitações fictícias. O modelo não será de muita utilidade neste tutorial, é apenas para dar uma ideia dos dados que o controlador deve manipular.
Antes de criar o modelo e o controlador, precisamos criar uma migração. Em um terminal — ou janela cmd.exe
, se você estiver usando o Windows — execute:
php artisan make:migration create_articles_table --create=articles
Agora, vá para a pasta database/migrations
e abra o arquivo com um nome semelhante a xxxx_xx_xx_xxxxxx_create_articles_table.php
.
Na função up
da classe, escreveremos isso:
Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->string('body'); $table->integer('user_id'); $table->timestamps(); });
Em seguida, criaremos um modelo de Article
. Para fazer isso, execute:
php artisan make:model Article
Em seguida, criamos o controlador ArticleController
executando:
php artisan make:controller ArticleController --resource
Em seguida, editaremos o arquivo app/Providers/AppServiceProvider.php
e importaremos a classe Illuminate\Support\Facades\Schema
adicionando:
use Illuminate\Support\Facades\Schema
…na parte inferior das importações na parte superior do arquivo.
Então, na função de boot
, escreveremos:
Schema::defaultStringLength(191);
Depois de tudo isso feito, podemos executar:
php artisan migrate
…para aplicar a migração que criamos acima.
Tutorial Laravel Passport, Etapa 2: Crie as peças necessárias de middleware
Aqui, adicionaremos os pedaços de middleware que serão necessários para que a API funcione.
Respostas JSON
A primeira peça necessária é o middleware ForceJsonResponse
, que converterá todas as respostas em JSON automaticamente.
Para fazer isso, execute:
php artisan make:middleware ForceJsonResponse
E esta é a função handle desse middleware, em App/Http/Middleware/ForceJsonReponse.php
:
public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); }
Em seguida, adicionaremos o middleware ao nosso arquivo app/Http/Kernel.php
no array $routeMiddleware
:
'json.response' => \App\Http\Middleware\ForceJsonResponse::class,
Em seguida, também o adicionaremos ao array $middleware
no mesmo arquivo:
\App\Http\Middleware\ForceJsonResponse::class,
Isso garantiria que o middleware ForceJsonResponse
fosse executado em todas as solicitações.
CORS (Compartilhamento de Recursos de Origem Cruzada)
Para permitir que os consumidores da nossa API REST do Laravel a acessem de uma origem diferente, precisamos configurar o CORS. Para fazer isso, criaremos um middleware chamado Cors
.
Em um terminal ou prompt de comando, cd
no diretório raiz do projeto e execute:
php artisan make:middleware Cors
Em seguida, em app/Http/Middleware/Cors.php
, adicione o seguinte código:
public function handle($request, Closure $next) { return $next($request) ->header('Access-Control-Allow-Origin', '*') ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, X-Token-Auth, Authorization'); }
Para carregar este middleware, precisaremos adicionar uma linha ao array $routeMiddleware
do app/Http/Kernel.php
:
'cors' => \App\Http\Middleware\Cors::class,
Além disso, teremos que adicioná-lo ao array $middleware
como fizemos para o middleware anterior:
\App\Http\Middleware\Cors::class,
Depois de fazer isso, anexaremos este grupo de rotas a routes/api.php
:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... });
Todas as nossas rotas de API irão para essa função, como veremos a seguir.
Tutorial Laravel Passport, Etapa 3: Criar controladores de autenticação de usuário para a API
Agora queremos criar o controlador de autenticação com as funções de login
e register
.
Primeiro, vamos executar:
php artisan make:controller Auth/ApiAuthController
Agora vamos importar algumas classes para o arquivo app/Http/Controllers/Auth/ApiAuthController.php
. Essas classes serão utilizadas na criação das funções de login
e register
. Vamos importar as classes adicionando:
use App\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str;
…para o topo do controlador.
Agora, para adicionar a autenticação da API do Laravel para nossos usuários, vamos criar as funções login
, logout
e register
(signup) no mesmo arquivo.
A função register
ficará assim:
public function register (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $request['password']=Hash::make($request['password']); $request['remember_token'] = Str::random(10); $user = User::create($request->toArray()); $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); }
A função de login
é assim:
public function login (Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:255', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $user = User::where('email', $request->email)->first(); if ($user) { if (Hash::check($request->password, $user->password)) { $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); } else { $response = ["message" => "Password mismatch"]; return response($response, 422); } } else { $response = ["message" =>'User does not exist']; return response($response, 422); } }
E, finalmente, a função de logout
:
public function logout (Request $request) { $token = $request->user()->token(); $token->revoke(); $response = ['message' => 'You have been successfully logged out!']; return response($response, 200); }
Após isso, precisamos adicionar as funções login
, register
e logout
às nossas rotas, ou seja, dentro do grupo de rotas já na API:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register','Auth\ApiAuthController@register')->name('register.api'); Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); // ... });
Por fim, precisamos adicionar o traço HasApiToken
ao modelo User
. Navegue até app/User
e certifique-se de ter:
use HasApiTokens, Notifiable;
… no topo da classe.
O que temos até agora…
Se iniciarmos o servidor de aplicação—ou seja, executar php artisan serve
—e então tentar enviar uma requisição GET
para a rota /api/user
, devemos receber a mensagem:
{ "message": "Unauthenticated." }
Isso ocorre porque não estamos autenticados para acessar essa rota. Para tornar algumas rotas de sua escolha protegidas, podemos adicioná-las a routes/api.php
logo após as linhas Route::post
:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here });
Antes de prosseguir, adicionaremos a rota de logout ao middleware auth:api
porque o Laravel usa um token para desconectar o usuário—um token que não pode ser acessado de fora do middleware auth:api
. Nossas rotas públicas são assim:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register', 'Auth\ApiAuthController@register')->name('register.api'); // ... });
Nossas rotas protegidas , por outro lado, são assim:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); });
Agora vamos navegar para o ArticleController
que criamos em app/Http/Controllers/ArticleController.php
e excluir os métodos create
e edit
nessa classe. Depois disso, adicionaremos o seguinte trecho de código, ligeiramente editado, a cada função restante:
$response = ['message' => '<function name> function']; return response($response, 200);
Vamos preencher <function name>
conforme apropriado. Por exemplo, a função de update
terá isso como seu corpo:
$response = ['message' => 'update function']; return response($response, 200);
Um teste manual de autenticação Laravel: criando um usuário
Para registrar um usuário, enviaremos uma solicitação POST
para /api/register
com os seguintes parâmetros: name
, email
(que deve ser exclusivo), password
e password_confirmation
.
Quando o usuário for criado, a API retornará um token, que usaremos em solicitações posteriores como nosso meio de autenticação.
Para fazer login, enviaremos uma solicitação POST
para /api/login
. Se nossas credenciais estiverem corretas, também obteremos um token da nossa API de login do Laravel dessa maneira.
O token de autorização que recebemos dessa solicitação podemos usar quando quisermos acessar uma rota protegida. No Postman, a guia “Authorization” tem um menu suspenso onde o tipo pode ser definido como “Bearer Token”, após o qual o token pode ir para o campo de token.
O processo é bastante semelhante em Insomnia.
Os usuários de cURL podem fazer o equivalente passando o parâmetro -H "Authorization: Bearer <token>"
, onde <token>
é o token de autorização fornecido a partir da resposta de login ou registro.
Assim como no cURL, se os desenvolvedores planejam consumir a API usando axios ou uma biblioteca desse tipo, eles podem adicionar um cabeçalho Authorization
com o valor Bearer <token>
.
Tutorial Laravel Passport, Etapa 4: Criar Funcionalidade de Redefinição de Senha
Agora que a autenticação básica está concluída, é hora de configurar uma função de redefinição de senha.
Para fazer isso, podemos optar por criar um diretório de controlador api_auth
, criar novos controladores personalizados e implementar a função; ou podemos editar os controladores de autenticação que podemos gerar com o Laravel. Nesse caso, editaremos os controladores de autenticação, já que toda a aplicação é uma API.

Primeiro, vamos gerar os controladores de autenticação executando:
composer require laravel/ui php artisan ui vue --auth
Editaremos a classe em app/Http/Controllers/Auth/ForgotPasswordController.php
, adicionando estes dois métodos:
protected function sendResetLinkResponse(Request $request, $response) { $response = ['message' => "Password reset email sent"]; return response($response, 200); } protected function sendResetLinkFailedResponse(Request $request, $response) { $response = "Email could not be sent to this email address"; return response($response, 500); }
Em seguida, precisamos configurar o controlador que realmente redefine a senha, então navegaremos para app/Http/Controllers/Auth/ResetPasswordController.php
e substituiremos as funções padrão assim:
protected function resetPassword($user, $password) { $user->password = Hash::make($password); $user->save(); event(new PasswordReset($user)); } protected function sendResetResponse(Request $request, $response) { $response = ['message' => "Password reset successful"]; return response($response, 200); } protected function sendResetFailedResponse(Request $request, $response) { $response = "Token Invalid"; return response($response, 401); }
Também precisamos importar algumas classes no controlador adicionando:
use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash;
…para o topo do controlador.
Também queremos modificar qual notificação de e-mail é usada, porque a notificação de e-mail que vem com o Laravel não usa tokens de API para autorização. Podemos criar um novo em app/Notifications
executando este comando:
php artisan make:notification MailResetPasswordNotification
Precisaremos editar o arquivo app/Notifications/MailResetPasswordNotification.php
para ficar assim:
<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Auth\Notifications\ResetPassword; use Illuminate\Support\Facades\Lang; class MailResetPasswordNotification extends ResetPassword { use Queueable; protected $pageUrl; public $token; /** * Create a new notification instance. * * @param $token */ public function __construct($token) { parent::__construct($token); $this->pageUrl = 'localhost:8080'; // we can set whatever we want here, or use .env to set environmental variables } /** * Get the notification's delivery channels. * * @param mixed $notifiable * @return array */ public function via($notifiable) { return ['mail']; } /** * Get the mail representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { if (static::$toMailCallback) { return call_user_func(static::$toMailCallback, $notifiable, $this->token); } return (new MailMessage) ->subject(Lang::getFromJson('Reset application Password')) ->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.')) ->action(Lang::getFromJson('Reset Password'), $this->pageUrl."?token=".$this->token) ->line(Lang::getFromJson('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')])) ->line(Lang::getFromJson('If you did not request a password reset, no further action is required.')); } /** * Get the array representation of the notification. * * @param mixed $notifiable * @return array */ public function toArray($notifiable) { return [ // ]; } }
Para fazer uso dessa nova notificação, precisamos substituir o método sendPasswordResetNotification
que User
herda da classe Authenticatable
. Tudo o que precisamos fazer é adicionar isso a app/User.php
:
public function sendPasswordResetNotification($token) { $this->notify(new \App\Notifications\MailResetPasswordNotification($token)); }
Com uma configuração de e-mail funcionando corretamente, as notificações devem estar funcionando neste momento.
Tudo o que resta agora é o controle de acesso do usuário.
Tutorial Laravel Passport, Etapa 5: Criar Middleware de Controle de Acesso
Antes de criarmos o middleware de controle de acesso, precisaremos atualizar a tabela de user
para ter uma coluna chamada type
, que será usada para determinar o nível do usuário: o tipo 0 é um usuário normal, o tipo 1 é um administrador e o tipo 2 é um superadministrador.
Para atualizar a tabela de user
, temos que criar uma migração executando isto:
php artisan make:migration update_users_table_to_include_type --table=users
No arquivo recém-criado do formulário database/migrations/[timestamp]_update_users_table.php
, precisaremos atualizar as funções up
e down
para adicionar e remover a coluna type
, respectivamente:
public function up() { Schema::table('users', function (Blueprint $table) { $table->integer('type'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropIfExists('type'); }); }
Em seguida, executaremos php artisan migrate
. Feito isso, temos que editar nossa função de register
no arquivo ApiAuthController.php
, adicionando isso logo antes da linha com $user = User::create($request->toArray());
:
$request['type'] = $request['type'] ? $request['type'] : 0;
Além disso, precisaremos adicionar esta linha ao array $validator
:
'type' => 'integer',
A primeira dessas duas edições fará com que todos os usuários registrados sejam “usuários normais” por padrão, ou seja, se nenhum tipo de usuário for inserido.
O próprio middleware de controle de acesso
Agora estamos em condições de criar dois middlewares para usar no controle de acesso: um para administradores e outro para superadministradores.
Então vamos executar:
php artisan make:middleware AdminAuth php artisan make:middleware SuperAdminAuth
Primeiro, navegaremos para app/Http/Middleware/AdminAuth.php
e importaremos Illuminate\Support\Facades\Auth
, depois editaremos a função handle
da seguinte forma:
public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 1) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }
Também precisaremos editar a função handle
em app/Http/Middleware/SuperAdminAuth.php
:
public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 2) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }
Você também deve importar a classe Auth
no topo de ambos os arquivos adicionando:
use Illuminate\Support\Facades\Auth;
… ao fundo das importações encontradas lá.
Para usar nosso novo middleware, faremos referência a ambas as classes no kernel—ou seja, em app/Http/Kernel.php
— adicionando as seguintes linhas ao array $routeMiddleware
:
'api.admin' => \App\Http\Middleware\AdminAuth::class, 'api.superAdmin' => \App\Http\Middleware\SuperAdminAuth::class,
Se os desenvolvedores quiserem usar o middleware em uma determinada rota, tudo o que você precisa fazer é adicioná-lo à função de rota assim:
Route::post('route','Controller@method')->middleware('<middleware-name-here>');
<middleware-name-here>
neste caso pode ser api.admin
, api.superAdmin
, etc., conforme apropriado.
Isso é tudo o que é necessário para criar nosso middleware.
Juntando tudo
Para testar se nossa autenticação e controle de acesso estão funcionando, há algumas etapas adicionais a serem seguidas.
Testando a autenticação do Laravel e o controle de acesso: Etapa 1
Precisamos modificar a função de index
do ArticleController
e registrar a rota. (Em projetos do mundo real, usaríamos o PHPUnit e faríamos isso como parte de um teste automatizado. Aqui, estamos adicionando manualmente uma rota para fins de teste - ela pode ser removida posteriormente.)
Navegaremos até o controlador ArticleController
em app/Http/Controllers/ArticleController
e modificaremos a função de index
para ficar assim:
public function index() { $response = ['message' => 'article index']; return response($response, 200); }
Em seguida, vamos registrar a função em uma rota acessando o arquivo routes/api.php
e anexando isto:
Route::middleware('auth:api')->group(function () { Route::get('/articles', 'ArticleController@index')->name('articles'); });
Testando a autenticação do Laravel e o controle de acesso: Etapa 2
Agora podemos tentar acessar a rota sem um token de autenticação. Devemos receber um erro de autenticação.
Testando a autenticação do Laravel e o controle de acesso: Etapa 3
Também podemos tentar acessar a mesma rota com um token de autorização (o que obtivemos ao registrar ou fazer login anteriormente neste artigo).
Às vezes, isso pode causar um erro semelhante a este:
Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ...
Se isso acontecer, os desenvolvedores devem certificar-se de ter executado uma migração do Passport e ter ['guards']['api']['driver']
definido como passport
em config/auth.php
:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
Depois disso, o cache de configuração também precisa ser atualizado.
Assim que isso for corrigido, devemos ter acesso à rota.
Testando a autenticação do Laravel e o controle de acesso: Etapa 4
É hora de testar o controle de acesso. Vamos anexar ->middleware('api.admin')
à rota de artigos, para que fique assim:
Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');
Fizemos com que um usuário recém-criado recebesse automaticamente o tipo 0, como podemos ver pela rota api/user
.
Por causa disso, devemos receber um erro ao tentar acessar o endpoint de articles
como um usuário.
Para fins de teste, vamos modificar o usuário no banco de dados para ter um type
de 1. Depois de verificar essa alteração pela rota api/user
novamente, estamos prontos para tentar novamente GET
a rota /articles/
.
Funciona perfeitamente.
Os desenvolvedores que estão criando aplicativos mais complexos devem observar que os controles de acesso adequados não serão tão simples. Nesse caso, outros aplicativos de terceiros ou portas e políticas do Laravel podem ser usados para implementar o controle de acesso personalizado do usuário. Na segunda parte desta série, veremos soluções de controle de acesso mais robustas e flexíveis.
Autenticação da API Laravel: o que aprendemos
Neste tutorial do Laravel Passport, discutimos:
- Criando um controlador e um modelo fictícios para ter algo para usar enquanto testamos nosso exemplo Laravel Passport.
- Criando o middleware necessário para que nossa API funcione sem problemas, endereçando CORS e forçando a API a sempre retornar respostas JSON.
- Configurando a autenticação básica da API do Laravel: registrando, entrando e saindo.
- Configurando a funcionalidade de “redefinição de senha” com base no padrão do Laravel.
- Criação de middleware de controle de acesso para adicionar níveis de permissão de autorização do usuário a diferentes rotas.
Essas são habilidades essenciais para quem trabalha na área de serviços de desenvolvimento Laravel. Os leitores encontrarão o resultado final neste repositório do GitHub e agora devem estar bem posicionados para implementar a autenticação com Laravel. Aguardamos comentários abaixo.