aut2fa/ blog

Código de verificação OTP no Laravel (PHP): tutorial passo a passo

Implemente envio e validação de código de verificação por WhatsApp em uma aplicação Laravel, com service, controller, rotas e tratamento de limites.

Código PHP com Laravel enviando um código de verificação OTP

Se a sua aplicação é em Laravel, adicionar um código de verificação por WhatsApp é questão de um service, um controller e duas rotas. Neste tutorial vamos fazer exatamente isso usando a API da aut2fa e o Http client nativo do framework.

Pré-requisitos

  • Laravel 10 ou superior.
  • Uma conta na aut2fa e uma chave de teste (ak_test_…). No modo de teste o código é sempre 000000 e nada é cobrado.

Prefere SDK? Existe o pacote oficial: composer require aut2fa/aut2fa-php.

1. Configuração

No .env:

AUT2FA_KEY=ak_test_xxxxxxxx

Em config/services.php:

'aut2fa' => [
    'key' => env('AUT2FA_KEY'),
    'url' => 'https://api.aut2fa.com/v1',
],

2. Service

<?php
// app/Services/Aut2fa.php
namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class Aut2fa
{
    private function client()
    {
        return Http::baseUrl(config('services.aut2fa.url'))
            ->withToken(config('services.aut2fa.key'))
            ->acceptJson()
            ->timeout(10);
    }

    public function enviar(string $telefone)
    {
        return $this->client()
            ->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
            ->post('/send', ['to' => $telefone, 'locale' => 'pt-BR']);
    }

    public function verificar(string $sendId, string $codigo)
    {
        return $this->client()
            ->post("/send/{$sendId}/verify", ['code' => $codigo]);
    }
}

3. Controller

<?php
// app/Http/Controllers/VerificacaoController.php
namespace App\Http\Controllers;

use App\Services\Aut2fa;
use Illuminate\Http\Request;

class VerificacaoController extends Controller
{
    public function enviar(Request $request, Aut2fa $aut2fa)
    {
        $dados = $request->validate([
            'telefone' => ['required', 'regex:/^\+55\d{10,11}$/'],
        ]);

        $res = $aut2fa->enviar($dados['telefone']);

        if ($res->status() === 429) {
            return response()->json([
                'erro' => 'Aguarde para pedir outro código.',
                'tentar_em' => (int) $res->header('Retry-After'),
            ], 429);
        }

        if ($res->status() !== 202) {
            return response()->json(['erro' => 'Falha ao enviar o código.'], 502);
        }

        session([
            'otp_send_id' => $res->json('id'),
            'otp_telefone' => $dados['telefone'],
        ]);

        return response()->json(['expira_em' => $res->json('expires_at')]);
    }

    public function verificar(Request $request, Aut2fa $aut2fa)
    {
        $dados = $request->validate(['codigo' => ['required', 'digits:6']]);

        $sendId = session('otp_send_id');
        abort_unless($sendId, 404, 'Nenhum código pendente.');

        $res = $aut2fa->verificar($sendId, $dados['codigo']);

        if ($res->json('valid') === true) {
            $request->user()->forceFill([
                'telefone' => session('otp_telefone'),
                'telefone_verificado_em' => now(),
            ])->save();

            session()->forget(['otp_send_id', 'otp_telefone']);
            return response()->json(['ok' => true]);
        }

        return response()->json([
            'ok' => false,
            'tentativas_restantes' => $res->json('attempts_left'),
        ], 422);
    }
}

4. Rotas

// routes/web.php
use App\Http\Controllers\VerificacaoController;

Route::middleware('auth')->group(function () {
    Route::post('/telefone/enviar', [VerificacaoController::class, 'enviar'])
        ->middleware('throttle:5,1');
    Route::post('/telefone/verificar', [VerificacaoController::class, 'verificar']);
});

O throttle:5,1 do Laravel é uma camada extra no seu lado. A aut2fa também aplica um teto de envios por minuto em toda conta e, se você ativar no painel, limites por aplicação por minuto e por destinatário por hora.

Como funciona por baixo

  • POST /v1/send responde 202 com o id do envio e expires_at.
  • A aut2fa gera um código de 6 dígitos, escolhe o canal e o número que envia e registra cada status com horário.
  • O código expira em 10 minutos; depois de 5 tentativas erradas, o envio é bloqueado.
  • O Idempotency-Key garante que um retry não gere um segundo código.
  • Se o código não for entregue, o envio não é cobrado.

Dica: mostre o status da entrega

Para exibir "código entregue" ou "lido" na tela, consulte GET /v1/send/{id}/status. Os estados são queued, sending, sent, delivered, read e failed.

Conclusão

Com um service e um controller, a sua aplicação Laravel passa a verificar telefones por WhatsApp com segurança e rastreabilidade. Consulte a documentação e crie sua conta grátis para testar.

Continue lendo