Autenticação completa do usuário e controle de acesso – um tutorial do Laravel Passport, Pt. 1

Publicados: 2022-03-11

Ao 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 .

Uma captura de tela do envio de uma solicitação POST para /api/register usando o Postman.

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.

Uma captura de tela do envio de uma solicitação POST para /api/login usando o Postman.

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.

Uma captura de tela do envio de uma solicitação GET para /api/articles usando o Postman, recebendo uma mensagem "Não autenticado" em resposta.

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` = ... 

Uma captura de tela do erro de coluna desconhecido ao obter a rota api/articles usando o Postman.

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.

Uma captura de tela do envio de uma solicitação GET para /api/articles usando o Postman, com uma resposta JSON normal.

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 .

Uma captura de tela do envio de uma solicitação GET para /api/user usando o Postman. A resposta inclui um ID, nome, e-mail, carimbo de data/hora de verificação de e-mail nulo, carimbos de data/hora preenchidos criados e atualizados e um tipo.

Por causa disso, devemos receber um erro ao tentar acessar o endpoint de articles como um usuário.

Uma captura de tela do envio de uma solicitação GET para /api/articles usando o Postman, com uma resposta JSON de permissão negada.

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/ .

Uma captura de tela do envio de uma solicitação GET para /api/articles como um usuário autenticado em Laravel usando o Postman, idêntica a uma solicitação anterior, com a exceção de que o tempo de resposta desta foi de 1.280 ms em vez de 890 ms.

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:

  1. Criando um controlador e um modelo fictícios para ter algo para usar enquanto testamos nosso exemplo Laravel Passport.
  2. 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.
  3. Configurando a autenticação básica da API do Laravel: registrando, entrando e saindo.
  4. Configurando a funcionalidade de “redefinição de senha” com base no padrão do Laravel.
  5. 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.