
Compartilhar:
Michael é um engenheiro de software poliglota, empenhado em reduzir a complexidade dos sistemas e torná-los mais previsíveis. Trabalhando com diversas linguagens e ferramentas, ele compartilha seus conhecimentos técnicos com públicos de todo o mundo em grupos de usuários e conferências. No dia a dia, Michael é ex-representante de desenvolvedores da Vonage, onde dedicava seu tempo a aprender, ensinar e escrever sobre todos os tipos de tecnologia.
Login com autenticação de dois fatores (2FA) usando Laravel e Nexmo
Esta postagem foi publicada originalmente em michaelheap.com antes de Michael ingressar na equipe da Nexmo equipe!
Recentemente, escrevi sobre como configurar o Laravel com autenticação de usuário e como isso é fácil (sério, leva menos de 5 minutos). Isso nos dá um ótimo ponto de partida para nossas Applications, mas então me deparei com esse post sobre a integração da autenticação de dois fatores com o Google Authenticator e comecei a pensar no Nexmo Verify.
Recentemente, eu integrei o Verify a um chatbot sem nenhum problema, e achei que poderia ser útil integrá-lo ao meu fluxo de login do Laravel.
Coleta do número de telefone do usuário
Precisamos coletar o número de telefone do usuário — sem ele, não podemos enviar uma mensagem de texto de verificação. Poderíamos coletar essa informação após o cadastro do usuário, mas decidi coletá-la já no momento do cadastro.
A primeira coisa que precisamos fazer é alterar a tabela “users” para que haja um campo pronto para armazenar o número de telefone do usuário. Para isso, vamos criar uma nova migração para alterar nossa users tabela:
Isso cria um arquivo na database/migrations pasta chamada <current time>_add_users_phone_number.php. Abra esse arquivo e substitua seu conteúdo pelo seguinte:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddUsersPhoneNumber extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('phone_number');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('phone_number');
});
}
}Essa migração adiciona uma coluna chamada phone_number quando executada e exclui a coluna quando revertida. Aplique-a agora executando php artisan migrate no seu terminal.
Em seguida, precisamos adicionar um campo de texto ao nosso formulário de cadastro para que o usuário insira seu número de telefone. Edite resources/views/auth/register.blade.php e adicione o seguinte ao final do formulário, logo antes do botão de envio:
<div class="form-group{{ $errors->has('phone_number') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">Phone Number</label>
<div class="col-md-6">
<input id="name" type="tel" class="form-control" name="phone_number" value="{{ old('phone_number') }}" required autofocus>
@if ($errors->has('phone_number'))
<span class="help-block">
<strong>{{ $errors->first('phone_number') }}</strong>
</span>
@endif
</div>
</div>Se acessarmos http://localhost:8000/register agora, devemos ver o campo de número de telefone na parte inferior do nosso formulário de cadastro. Estamos quase lá, mas ainda falta uma parte essencial: na verdade, não estamos salvando o número que o usuário insere nesse novo campo no banco de dados.
O Laravel mantém toda a sua lógica para registrar um usuário no app/Http/Controllers/Auth/RegisterController.php arquivo. Abra-o e dê uma olhada — você deverá ver um validator método e um create método. Precisaremos alterar ambos para salvar o número de telefone do nosso usuário.
Vamos começar com o validator método. Precisamos adicionar uma nova entrada para phone_number garantir que o número fornecido seja válido. Optei por ser bastante rigoroso com minhas regras de validação, exigindo que ele tenha exatamente 12 caracteres e seja único entre todos os usuários — você pode optar por ser menos rigoroso. Depois de adicionar uma regra de validação, seu validator método deve ficar parecido com o seguinte:
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
'phone_number' =>; 'required|size:12|unique:users',
]);Depois que esses dados passarem pelas regras de validação que especificamos, precisamos armazená-los no banco de dados. Para isso, editamos o create método e adicionamos uma linha que salva nosso número de telefone. Todos os dados da solicitação recebida estão disponíveis na $data variável, portanto, basta adicionar uma única linha:
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'phone_number' =>; $data['phone_number']
]);
Se tentarmos adicionar um usuário agora, isso não funcionará como esperado. Isso se deve a um recurso de segurança do Laravel que impede a atribuição em massa de propriedades a uma classe. Ainda não informamos nossa User classe de que phone_number é um campo válido; por isso, ela rejeitará nossa solicitação para salvá-lo. Para resolver esse problema, edite app/User.php e adicione phone_number ao $fillable matriz:
protected $fillable = [
'name', 'email', 'password', 'phone_number'
];Depois de fazer essa alteração, fique à vontade para criar um Account pela página de cadastro e fazer login em nosso aplicativo.
Adicionando o Nexmo Verify
Agora que temos o número de telefone do usuário, estamos prontos para começar a implementar nossa lógica Verify. O Laravel submete a solicitação de login do usuário a app/Http/Controllers/Auth/LoginController.php para verificar se as credenciais fornecidas são válidas ou não. Se as credenciais forem válidas, o Laravel procurará um authenticated método no arquivo LoginController. Se o método existir, ele executará a lógica contida nele. É aqui que adicionaremos nossa lógica de autenticação de dois fatores.
Abra app/Http/Controllers/Auth/LoginController.php e adicione o seguinte no início, ao lado das outras use declarações:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Contracts\Auth\Authenticatable;Precisamos dessas três use declarações para podermos definir a tipagem do nosso authenticated método, que devemos adicionar a seguir. Adicione o seguinte à LoginController classe:
public function authenticated(Request $request, Authenticatable $user)
{
Auth::logout();
$request->session()->put('verify:user:id', $user->id);
// @TODO: Send the Verify SMS here
return redirect('verify');
}
Esse código fará com que o usuário seja desconectado novamente, armazenando seu ID de usuário na sessão para que possamos saber com qual conta ele tentou fazer login. Assim que a solicitação de verificação for concluída, usaremos esse ID para reconectar o usuário automaticamente.
Acionando uma solicitação de Verify
Você deve ter notado que há um @TODO ali para adicionar a lógica de verificação por SMS. No momento, ainda não temos como enviar um SMS pelo Nexmo, então vamos resolver isso a seguir. Felizmente, o Nexmo tem um pacote para Laravel que facilita bastante essa tarefa para nós. Seguindo o arquivo README desse projeto, instalamos tanto o cliente Nexmo quanto o provedor de serviços do Laravel com o Composer:
Depois de instalado, precisamos informar ao Laravel que nosso cliente existe. Precisamos editar duas seções em config/app.php para fazer isso — providers e aliases.
Adicione o seguinte a providers:
Nexmo\Laravel\NexmoServiceProvider::classAdicione o seguinte a aliases:
'Nexmo' => \Nexmo\Laravel\Facade\Nexmo::class
Por fim, precisamos executar php artisan vendor:publish para gerar nosso arquivo de configuração do Nexmo. Depois de executar esse comando, podemos editar config/nexmo.php e inserir nossas credenciais de API em api_key e api_secret. Podemos inseri-las diretamente aqui ou usar o .env , semelhante ao arquivo de configuração do banco de dados. Vou usar o .env arquivo, então alterei config/nexmo.php para que ele contenha o seguinte:
'api_key' => env('NEXMO_KEY', ''),
'api_secret' => env('NEXMO_SECRET', ''),
Em seguida, em .env, adicionei duas entradas no final do arquivo — NEXMO_KEY e NEXMO_SECRET:
Agora que o cliente Nexmo está configurado, podemos voltar para app/Http/Controllers/Auth/LoginController.php e implementar nosso sistema de notificações. Substitua o @TODO comentário que deixamos pelo seguinte:
$verification = Nexmo::verify()->start([
'number' => $user->phone_number,
'brand' => 'Laravel Demo'
]);
$request->session()->put('verify:request_id', $verification->getRequestId());
Isso acionará uma solicitação de verificação via Nexmo para o número de telefone que temos registrado para esse usuário. Também precisaremos adicionar use Nexmo; no início do arquivo para que nossa fachada esteja disponível. Depois de fazer isso, você poderá fazer login e acionar uma solicitação de verificação — mas não faça isso ainda! Não temos como o usuário fornecer seu código de verificação, então você não conseguirá confirmar sua identidade.
Verificação da solicitação
No final de LoginController::authenticated , redirecionamos o usuário para uma /verify URL. É hora de registrar essa rota no Laravel e escrever uma implementação para ela.
Abra routes/web.php e adicione o seguinte ao final do arquivo:
Route::get('/verify', 'VerifyController@show')->name('verify');
Route::post('/verify', 'VerifyController@verify')->name('verify');
Isso registra duas rotas (uma GET e uma POST para /verify) que usaremos para verificar o código de um usuário. Informamos ao Laravel que ele deve chamar o show e verify métodos na VerifyController para essas solicitações; portanto, devemos gerar o controlador usando artisan:
php artisan make:controller VerifyControllerIsso criará um arquivo em app/Http/Controllers/VerifyController.php - você deve substituir o conteúdo dele pelo seguinte:
<?php
namespace App\Http\Controllers;
use Auth;
use Nexmo;
use Illuminate\Http\Request;
class VerifyController extends Controller
{
public function show(Request $request) {
return view('verify');
}
public function verify(Request $request) {
return 'Not Implemented';
}
}Isso é o suficiente para exibir a visualização “Verify” quando alguém fizer uma solicitação GET para /verify. Mais uma vez, esse arquivo ainda não existe, então vamos criá-lo em resources/views/verify.blade.php com o seguinte conteúdo:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Verify</div>
<div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ route('verify') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('code') ? ' has-error' : '' }}">
<label for="code" class="col-md-4 control-label">Code</label>
<div class="col-md-6">
<input id="code" type="number" class="form-control" name="code" value="{{ old('code') }}" required autofocus>
@if ($errors->has('code'))
<span class="help-block">
<strong>{{ $errors->first('code') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Verify Account
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsectionHá bastante código HTML ali, mas tudo o que ele faz é exibir um único campo de formulário com um botão de envio. Você pode acessar a página de verificação para vê-lo agora.
Agora que já temos nossa página para inserir o código Verify, só falta verificar o código fornecido pela Nexmo. Substitua seu verify método em VerifyController pelo código a seguir. Esse método verifica se os dados recebidos têm 4 caracteres (os códigos de verificação do Nexmo podem ter 4 ou 6 caracteres; estou trabalhando com 4) e, em seguida, verifica o código fornecido com o Nexmo. Se a verificação falhar, uma exceção é lançada e retornamos um erro ao usuário. Caso contrário, buscamos o ID do usuário na sessão, fazemos o login do usuário e redirecionamos para o controlador da página inicial.
public function verify(Request $request) {
$this->validate($request, [
'code' => 'size:4',
]);
try {
Nexmo::verify()->check(
$request->session()->get('verify:request_id'),
$request->code
);
Auth::loginUsingId($request->session()->pull('verify:user:id'));
return redirect('/home');
} catch (Nexmo\Client\Exception\Request $e) {
return redirect()->back()->withErrors([
'code' => $e->getMessage()
]);
}
}Neste momento, nossa integração já deve estar funcionando de ponta a ponta. Se você salvar todas as alterações e tentar fazer login, deverá ser redirecionado para a verify página e receber uma mensagem de texto com seu código de verificação. Digite o código e você será conectado, conforme esperado.
Parabéns! Você acabou de integrar a autenticação de dois fatores com o Nexmo Verify à sua aplicação Laravel.
Ajeitando as imperfeições
Embora funcione, ainda há alguns detalhes a serem resolvidos. Por exemplo, um usuário pode fazer login pela segunda vez sem confirmar a primeira solicitação de Verify. Ele também pode acessar a /verify página sem ter uma solicitação de Verify ativa. Por fim, não verificamos a identidade do usuário após o cadastro — apenas depois que ele faz logout e tenta fazer login novamente.
Não vamos resolver essas questões neste post — vou deixá-las como um exercício para vocês!
Compartilhar:
Michael é um engenheiro de software poliglota, empenhado em reduzir a complexidade dos sistemas e torná-los mais previsíveis. Trabalhando com diversas linguagens e ferramentas, ele compartilha seus conhecimentos técnicos com públicos de todo o mundo em grupos de usuários e conferências. No dia a dia, Michael é ex-representante de desenvolvedores da Vonage, onde dedicava seu tempo a aprender, ensinar e escrever sobre todos os tipos de tecnologia.