2FA no Laravel com Fortify: Na Parte 3 definimos nosso projeto de exemplo, o Gerenciador de Tarefas, e nas anteriores configuramos a base do sistema. Agora, na sequência da nossa série sobre desenvolvimento web com a pilha Laravel 12 + Breeze + Fortify + Tailwind CSS + Alpine.js + Vite, chegamos a uma etapa crucial: implementar o Fortify e configurar a autenticação em dois fatores (2FA).
O Fortify é um backend para autenticação no Laravel que fornece, de forma desacoplada, funcionalidades robustas como:
- Login e registro;
- Confirmação de senha;
- Recuperação e redefinição de senha;
- Autenticação em dois fatores (2FA);
- Verificação de e-mail (opcional no Laravel 12).

🎯 Por que usar Fortify junto com Breeze?
Relembrando, o Laravel Jetstream já inclui tudo isso, inclusive uma interface de autenticação robusta e 2FA, mas decidimos optar pelo Breeze por dois motivos:
- Interface mais simples e flexível, ideal para quem deseja customizar a aparência e comportamento das telas;
- Curva de aprendizado menor, com uma estrutura baseada em Blade e Tailwind, que facilita ajustes visuais e lógicos.
Com isso, usamos o Fortify apenas para a lógica de autenticação no backend e o Breeze para a interface frontend.
✅ O que faremos nesta etapa:
- Instalar e configurar o Laravel Fortify
- Ativar e implementar a autenticação em dois fatores (2FA)
- Realizar os ajustes necessários em Model, Controller, Views, Rotas e Middleware
- Tudo isso integrado ao ambiente que configuramos nas partes anteriores
📦 Passo 1: Instalar o Laravel Fortify
No terminal, dentro do seu projeto Laravel, execute:
composer require laravel/fortify
✅ Observação: Fortify não tem dependências de frontend, portanto não precisa rodar
npm installounpm run devapós a instalação. Ele é 100% backend.
🔧 Passo 2: Publicar as Configurações do Fortify
Execute o comando:
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
Isso irá gerar o arquivo de configuração e o provider para o Fortify:
config/fortify.php app/Providers/FortifyServiceProvider.php
✔️ Observações Importantes no Laravel 12:
- O Fortify já vem com o 2FA habilitado por padrão (suporte completo para 2FA), com as melhores práticas ativadas.
- As rotas para o 2FA, login, registro, senha e perfil são fornecidas automaticamente pelo Fortify.
- A verificação de e-mail é opcional (vem comentada por padrão no arquivo
config/fortify.php). - O Laravel 12 utiliza auto-discovery de service providers, portanto não é mais necessário registrá-los manualmente no
config/app.php.
🔍 Trecho do arquivo config/fortify.php:
'features' => [
Features::registration(),
Features::resetPasswords(),
// Features::emailVerification(),
Features::updateProfileInformation(),
Features::updatePasswords(),
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
// 'window' => 0,
]),
],
👉 Dica: Mesmo que não use a confirmação de e-mail, descomente Features::emailVerification(). Em teoria não precisaria, na prática evita-se alguns erros de lógica no uso do Larevel Fortify.
Por padrão, o Fortify redireciona para /home. No nosso projeto, queremos redirecionar para /dashboard.
Ainda no arquivo config/fortify.php, altere:
'home' => '/dashboard',
🧠Passo 3 – Configurar o FortifyServiceProvider
O Laravel Fortify precisa de um Service Provider para definir quais views serão utilizadas no login, registro, redefinição de senha, entre outras funcionalidades. Para isso, no passo anterior, além de criar o config/fortify.php, criamos, de forma automática, o , que agora iremos configurar.app/Providers/FortifyServiceProvider.php
Edite esse arquivo com o seguinte conteúdo:
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use Laravel\Fortify\Contracts\ResetsUserPasswords;
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(CreatesNewUsers::class, CreateNewUser::class);
$this->app->singleton(UpdatesUserProfileInformation::class, UpdateUserProfileInformation::class);
$this->app->singleton(UpdatesUserPasswords::class, UpdateUserPassword::class);
$this->app->singleton(ResetsUserPasswords::class, ResetUserPassword::class);
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Definir as views personalizadas
Fortify::loginView(fn () => view('auth.login'));
Fortify::registerView(fn () => view('auth.register'));
Fortify::requestPasswordResetLinkView(fn () => view('auth.forgot-password'));
Fortify::resetPasswordView(fn ($request) => view('auth.reset-password', ['request' => $request]));
Fortify::confirmPasswordView(fn () => view('auth.confirm-password'));
Fortify::twoFactorChallengeView(fn () => view('auth.two-factor-challenge'));
// A view de verificação de e-mail foi removida, pois não utilizamos
// Caso queira usar no futuro, descomente:
// Fortify::verifyEmailView(fn () => view('auth.verify-email'));
// Definir manualmente a autenticação (opcional, mas recomendado)
Fortify::authenticateUsing(function (Request $request) {
$user = User::where('email', $request->email)->first();
if (
$user &&
Hash::check($request->password, $user->password)
) {
return $user;
}
return null;
});
}
}
Observação: Esse código pressupõe que você tenha as views correspondentes dentro da pasta:
resources/views/auth/Por exemplo:
auth/login.blade.phpauth/register.blade.phpauth/forgot-password.blade.phpauth/reset-password.blade.phpauth/verify-email.blade.phpauth/two-factor-challenge.blade.phpauth/confirm-password.blade.phpComo estamos usando o Breeze, essas views já existem, com exceção de a
uth/two-factor-challenge.blade.php, que iremos criar.
Importante: Como, no passo anterior, não utilizamos o
artisanpara criar o provider, precisamos registrá-lo manualmente embootstrap/providers.php.
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
];
🧠 Passo 4 – Configurar o Rate Limiter (Novo no Laravel 12)
Agora, você precisa criar um Rate Limiter.
Para isso, vamos criar o RateLimiterServiceProvider com o Artisan.
No terminal, execute:
php artisan make:provider RateLimiterServiceProvider
Esse comando criará o arquivo app/Providers/RateLimiterServiceProvider.php.
Agora, edite esse arquivo com o seguinte conteúdo:
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
class RateLimiterServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->email.$request->ip());
});
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
}
}
Diferente do
, como usamos oapp/Providers/FortifyServiceProvider.phpartisan, esse novo provider já foi registrado nobootstrap/providers.php.
👤 Passo 5: Ajustar a Tabela “Users” e o Model “User”
Agora precisamos rodar as migrations para criar as tabelas necessárias para o 2FA. O Fortify adiciona colunas na tabela de usuários para armazenar os dados do 2FA:
php artisan migrate
Isso vai adicionar colunas como two_factor_secret, two_factor_recovery_codes, etc. na tabela users.
🔧 Observação:
Quando você executa php artisan migrate, o Fortify incluiu automaticamente suas migrations que adicionam as colunas necessárias para 2FA na tabela users existente.
Como funciona:
- O Fortify vem com migrations pré-definidas
- Essas migrations fazem
ALTER TABLEna tabelausers - Adicionam colunas como
two_factor_secret,two_factor_recovery_codes,two_factor_confirmed_at
Vamos agora ajustar o Model User para o 2FA, no arquivo:
app/Models/User.php
Adicione o trait: TwoFactorAuthenticatable, que vai permitir que o modelo User trabalhe com as funcionalidades de 2FA.
use Laravel\Fortify\TwoFactorAuthenticatable;
class User extends Authenticatable
{
use HasFactory, Notifiable, TwoFactorAuthenticatable;
...
}
Além disso, adicione os campos relacionados ao 2FA no array de $hidden e $casts (essas configurações no modelo User são para proteger os dados sensíveis do 2FA):
protected $hidden = [
...
'two_factor_recovery_codes',
'two_factor_secret',
];
protected $casts = [
...
'two_factor_confirmed_at' => 'datetime',
];
app/Models/User.php completo:
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, TwoFactorAuthenticatable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
'two_factor_recovery_codes',
'two_factor_secret',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'two_factor_confirmed_at' => 'datetime',
];
}
}
🎨 Passo 6 — Criar as Views do 2FA: Gerenciamento e Desafio
Para que a Autenticação em Dois Fatores (2FA) funcione corretamente com o Laravel Fortify, precisamos criar duas views específicas. Cada uma possui um papel distinto no fluxo de autenticação.
🔹 6.1. View de Gerenciamento (two-factor.blade.php)
Por que criar?
Essa view é usada dentro da tela de perfil do usuário para permitir que ele ative, desative ou visualize o status da autenticação em dois fatores. Ela exibe o QR Code, códigos de recuperação e exige confirmação da senha.
Onde colocar:resources/views/profile/two-factor.blade.php
O que essa view faz?
- ✔️ Verifica se o 2FA está ativado (
$enabled). - ✔️ Se está ativado:
- Mostra o QR Code.
- Exibe os códigos de recuperação.
- Permite regenerar os códigos.
- Permite desativar o 2FA.
- ✔️ Se está desativado:
- Oferece um botão para ativar o 2FA.
- ✔️ Inclui um link para voltar à tela de edição de perfil.
Conteúdo:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Two-Factor Authentication (2FA)') }}
</h2>
</x-slot>
<div class="py-6">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">
{{ __('2FA Status:') }}
</h3>
{{-- Exibe uma mensagem de status se a 2FA foi habilitada e precisa de confirmação --}}
@if (session('status') == 'two-factor-authentication-enabled')
<div class="mb-4 font-medium text-sm text-green-600">
{{ __('Two-factor authentication has been enabled. Please confirm by entering a code.') }}
</div>
@endif
{{-- Bloco para quando a autenticação de dois fatores está habilitada, mas ainda não confirmada --}}
{{-- Verifica se a 2FA está habilitada ($enabled é true) E se a confirmação ainda não foi realizada (two_factor_confirmed_at é NULL) --}}
@if ($enabled && is_null(Auth::user()->two_factor_confirmed_at))
<div class="text-yellow-600 font-semibold mb-4">
{{ __('Two-Factor Authentication is PENDING CONFIRMATION.') }}
</div>
<div class="mb-6">
<p class="mb-2">{{ __('Scan this QR Code with your authenticator app (Google Authenticator, Authy, etc.):') }}</p>
{{-- Exibe o QR Code SVG. O $qrCode é passado pelo ProfileController --}}
<div>{!! $qrCode !!}</div>
</div>
<p class="mb-4 text-sm text-gray-600">
{{ __('To finish enabling two factor authentication, scan the following QR code using your phone\'s authenticator application or enter the setup key and provide the generated OTP code.') }}
</p>
{{-- Formulário para o usuário inserir o código de autenticação e confirmar a 2FA --}}
{{-- Este formulário envia o código para a rota 'two-factor.confirm' do Fortify --}}
<form method="POST" action="{{ route('two-factor.confirm') }}">
@csrf
<div class="mt-4">
<label for="code" class="block font-medium text-sm text-gray-700">{{ __('Authentication Code') }}</label>
<input id="code" type="text" name="code" class="mt-1 block w-full" inputmode="numeric" autofocus autocomplete="one-time-code" />
{{-- Exibe erros de validação para o campo 'code' --}}
@error('code')
<span class="text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
<div class="mt-4 flex items-center justify-end">
<x-primary-button>{{ __('Confirm') }}</x-primary-button>
</div>
</form>
{{-- Bloco para quando a autenticação de dois fatores está habilitada E confirmada --}}
{{-- Verifica se a 2FA está habilitada ($enabled é true) E se a confirmação JÁ foi realizada (two_factor_confirmed_at NÃO é NULL) --}}
@elseif ($enabled && !is_null(Auth::user()->two_factor_confirmed_at))
<div class="text-green-600 font-semibold mb-4">
{{ __('Two-Factor Authentication is ENABLED.') }}
</div>
<div class="mb-6">
<p class="mb-2">{{ __('Scan this QR Code with your authenticator app (Google Authenticator, Authy, etc.):') }}</p>
{{-- Exibe o QR Code SVG. O $qrCode é passado pelo ProfileController --}}
<div>{!! $qrCode !!}</div>
</div>
<div class="mb-6">
<h4 class="font-semibold mb-2">{{ __('Recovery Codes:') }}</h4>
<ul class="list-disc pl-5">
{{-- Itera e exibe os códigos de recuperação. Os $recoveryCodes são passados pelo ProfileController --}}
@foreach ($recoveryCodes as $code)
<li>{{ $code }}</li>
@endforeach
</ul>
{{-- Formulário para regenerar os códigos de recuperação --}}
{{-- Ação para a rota 'two-factor.recovery-codes' --}}
<form method="POST" action="{{ route('two-factor.recovery-codes') }}" class="mt-4">
@csrf
<x-primary-button>{{ __('Regenerate Recovery Codes') }}</x-primary-button>
</form>
</div>
{{-- Formulário para desabilitar a autenticação de dois fatores --}}
{{-- Ação para a rota 'two-factor.disable' --}}
<form method="POST" action="{{ route('two-factor.disable') }}">
@csrf
@method('DELETE')
<x-danger-button>{{ __('Disable 2FA') }}</x-danger-button>
</form>
{{-- Bloco para quando a autenticação de dois fatores está desabilitada --}}
@else
<div class="text-red-600 font-semibold mb-4">
{{ __('Two-Factor Authentication is DISABLED.') }}
</div>
{{-- Formulário para habilitar a autenticação de dois fatores --}}
{{-- Ação para a rota 'two-factor.enable' --}}
<form method="POST" action="{{ route('two-factor.enable') }}">
@csrf
<x-primary-button>{{ __('Enable 2FA') }}</x-primary-button>
</form>
@endif
{{-- Botão para voltar para as configurações de perfil --}}
<div class="mt-8">
<a href="{{ route('profile.edit') }}"
class="inline-flex items-center px-4 py-2 bg-gray-100 border border-gray-300
rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest
hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2
focus:ring-gray-500 transition">
{{ __('Back to Profile') }}
</a>
</div>
</div>
</div>
</div>
</x-app-layout>
Observação: Você deve incluir essa view como um item do menu de usuário, logo abaixo da opção “Profile/Perfil”.
Até lá, acesse como, por exemplo
http://taskmanager.test/profile/two-factor. (Vide Passo Extra 2)
🔹 6.2. View do Desafio de Login (two-factor-challenge.blade.php)
Por que criar?
Depois que o usuário faz login com e-mail e senha, se o 2FA estiver habilitado, o Fortify redireciona para essa view, que solicita o código gerado pelo aplicativo autenticador.
Onde colocar:resources/views/auth/two-factor-challenge.blade.php
Conteúdo:
<x-guest-layout>
<div class="mb-4 text-sm text-gray-600">
{{ __('Please confirm access to your account by entering the authentication code provided by your authenticator application.') }}
</div>
@if (session('status'))
<div class="mb-4 font-medium text-sm text-green-600">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('two-factor.login') }}">
@csrf
<!-- Authentication Code -->
<div>
<label for="code" class="block font-medium text-sm text-gray-700">{{ __('Authentication Code') }}</label>
<input id="code" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" type="text" name="code" autofocus autocomplete="one-time-code" />
@error('code')
<div class="text-red-500 text-sm">{{ $message }}</div>
@enderror
</div>
<!-- Submit Button -->
<div class="flex items-center justify-center mt-4">
<button
type="submit"
class="inline-flex items-center px-4 py-2 bg-blue-600 border border-transparent rounded-md font-semibold text-sm text-white uppercase tracking-widest hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
{{ __('Verify') }}
</button>
</div>
</form>
</x-guest-layout>
🛠️ Registrando a View no Fortify
Garanta que a view do desafio (two-factor-challenge) esteja registrada no seu FortifyServiceProvider (vide Passo 3):
Fortify::twoFactorChallengeView(fn () => view('auth.two-factor-challenge'));
Com essas duas views, o sistema estará quase preparado para oferecer uma experiência completa de autenticação em dois fatores, tanto no gerenciamento pelo usuário quanto na proteção do login.
🧩Passo 7 — Organizar as Rotas de Autenticação e Perfil
Neste passo, vamos organizar os arquivos de rota routes/auth.php e routes/web.php para garantir compatibilidade com o Fortify no Laravel 12, evitar duplicações e assegurar que o gerenciamento de perfil e autenticação em dois fatores (2FA) funcione corretamente, sem a necessidade de alterar a view edit.blade.php do Breeze.
✅ Contexto do Projeto
- Laravel 12 com Breeze + Fortify.
- As views de login, registro, recuperação de senha e 2FA são definidas diretamente no
FortifyServiceProvider.php. - O Fortify já registra automaticamente as rotas para autenticação.
- O objetivo é centralizar as rotas da aplicação e permitir acesso direto à página de gerenciamento de 2FA via menu.
Ao integrar o Laravel Breeze, é comum que ele gere o arquivo
routes/auth.phpcontendo diversas rotas de autenticação (como login, registro, etc.). No entanto, como estamos utilizando o Fortify como backend de autenticação e configuramos umFortifyServiceProviderespecífico para definir as views e o comportamento desses processos, o Fortify já se encarrega de registrar suas próprias rotas internas para essas funcionalidades.Ter as mesmas rotas declaradas tanto em
routes/auth.phpquanto implicitamente pelo Fortify (e às vezes explicitamente emroutes/web.php) resulta em duplicação desnecessária.Embora o Laravel geralmente resolva escolhendo a primeira rota que corresponde, essa redundância pode levar a confusões, dificultar a manutenção e, em cenários mais complexos, gerar erros de lógica ou comportamento inesperado, tornando a limpeza e centralização dessas rotas uma prática recomendada.
7.1. Limpar o arquivo routes/auth.php
Este arquivo deve ser minimalista, já que o Fortify já lida internamente com login, registro e autenticação. Manter rotas duplicadas pode causar conflitos ou confusão.
🛠️ Ação:
Remover rotas de /profile ou quaisquer duplicações de rotas já tratadas no FortifyServiceProvider.
📄 routes/auth.php:
<?php
use Illuminate\Support\Facades\Route;
Route::middleware('auth')->group(function () {
// Todas as rotas de perfil duplicadas foram removidas daqui.
// Este arquivo agora deve estar vazio neste grupo ou conter apenas rotas muito específicas
// de autenticação não gerenciadas pelo Fortify.
// Este arquivo pode ficar minimalista/vazio.
});
💡 Se você deseja criar endpoints personalizados não gerenciados pelo Fortify, poderá adicioná-los aqui no futuro.
7.2. Ajustar o arquivo routes/web.php
Este é o local apropriado para definir as rotas da aplicação após o login, incluindo dashboard, perfil e a nova tela de gerenciamento de 2FA.
🛠️ Ações:
- Mantenha as rotas de
/e/dashboard. - Centralize as rotas de
/profileaqui, dentro deautheverified. - Inclua a nova rota dedicada para a tela de 2FA:
/profile/two-factor.
📄 routes/web.php:
<?php
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware(['auth', 'verified'])->group(function () {
// Perfil do usuário
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// Gerenciamento de 2FA
Route::get('/profile/two-factor', [ProfileController::class, 'showTwoFactorForm'])
->name('profile.two-factor');
});
require __DIR__.'/auth.php';
💡 Separar a tela de gerenciamento de 2FA em uma rota própria permite exibi-la como uma página autônoma acessada diretamente por menu ou link.
7.3. Verificar o FortifyServiceProvider.php
Certifique-se de que suas views estão corretamente registradas para que o Fortify saiba qual tela exibir em cada fluxo.
🛠️ Ação:
Verifique se as definições de view estão presentes no método boot(). – já realizado n o Passo 3.
📄 Trecho de Exemplo — FortifyServiceProvider.php:
// Definir as views personalizadas
Fortify::loginView(fn () => view('auth.login'));
Fortify::registerView(fn () => view('auth.register'));
✅ Resumo Final da Organização das Rotas
| Arquivo | Responsabilidade |
|---|---|
FortifyServiceProvider | Define as views usadas pelo Fortify. |
routes/auth.php | Mínimo ou vazio. Usado para rotas de autenticação muito específicas. |
routes/web.php | Contém as rotas principais da aplicação (dashboard, perfil, gerenciamento 2FA). |
Com essa organização, o sistema de rotas estará enxuto, bem estruturado, sem conflitos e totalmente compatível com o Laravel 12 + Breeze + Fortify.
🧩 Passo 8 — Ajustar o ProfileController para Suporte ao 2FA
Agora que organizamos corretamente as rotas e criamos uma view dedicada para a página de gerenciamento da autenticação em dois fatores (2FA), precisamos ajustar o ProfileController para alimentar essa página com os dados corretos.
Sem isso, a view profile.two-factor.blade.php não terá acesso ao estado atual do 2FA (habilitado ou não), ao QR Code para ativação e aos códigos de recuperação.
🎯 Objetivo deste passo
Criar um método no ProfileController chamado showTwoFactorForm, que será responsável por:
- Verificar se o 2FA está habilitado para o usuário logado.
- Gerar o QR Code de ativação do 2FA.
- Obter os códigos de recuperação.
- Enviar esses dados para a view de gerenciamento do 2FA.
✍️ Implementação no ProfileController.php
Abra o arquivo app/Http/Controllers/ProfileController.php e adicione o seguinte método:
/**
* Exibir a tela de gerenciamento de autenticação em dois fatores (2FA).
*/
public function showTwoFactorForm(Request $request): \Illuminate\View\View
{
$user = $request->user();
// Inicialize as variáveis com valores padrão nulos/vazios
$qrCode = null;
$recoveryCodes = [];
// SOMENTE gere o QR Code e os códigos de recuperação SE o 2FA estiver ativado
if (! is_null($user->two_factor_secret)) {
$qrCode = $user->twoFactorQrCodeSvg();
$recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true); // Decrypt e decode
}
return view('profile.two-factor', [
'user' => $user,
'enabled' => !is_null($user->two_factor_secret),
'qrCode' => $qrCode, // Agora pode ser null se 2FA estiver desabilitado
'recoveryCodes' => $recoveryCodes, // Agora pode ser vazio se 2FA estiver desabilitado
]);
}
🔐 Este método utiliza os recursos internos do Fortify (já disponíveis no modelo
User) para recuperar todos os dados necessários à tela de gerenciamento do 2FA.
🧠 Explicando o que cada item faz:
two_factor_secret: campo armazenado no banco que indica se o 2FA está ativo.twoFactorQrCodeSvg(): método do Fortify que gera o código QR no formato SVG.two_factor_recovery_codes: são os códigos de backup criptografados.decrypt()+json_decode(): decodificam esses códigos para serem exibidos na view.
✅ Resultado esperado
Com este método implementado, ao acessar a rota /profile/two-factor (já criada no web.php), o usuário verá uma interface funcional para:
- Ativar ou desativar o 2FA.
- Escanear o QR Code com um app autenticador (como Google Authenticator ou Authy).
- Copiar seus códigos de recuperação.
🔥 Esclarecimento Importante:
O Laravel Fortify já fornece as rotas responsáveis por ativar, desativar e gerenciar o 2FA no backend. Contudo, para oferecer uma interface onde o usuário possa visualizar o status do 2FA (ativado ou não), acessar o QR Code e seus códigos de recuperação, foi preciso criar um método no nosso ProfileController.
Nota: O Laravel Fortify cria automaticamente rotas para as ações do 2FA, como:
- Ativar 2FA →
/user/two-factor-authentication(POST) - Desativar 2FA →
/user/two-factor-authentication(DELETE) - Gerar códigos de recuperação →
/user/two-factor-recovery-codes(POST) - Desafio no login (quando 2FA está ativado) →
/two-factor-challenge(GET / POST)
✅ Essas rotas são internas do Fortify e operam via APIs ou requests HTTP, sem exibir telas.
➡️ O Fortify NÃO cria uma rota para uma página de gerenciamento do 2FA, como
/profile/two-factor. Essa é uma responsabilidade do desenvolvedor criar, se desejar uma interface visual.
📌 Conclusão do Passo 8
Esse ajuste finaliza a integração da funcionalidade 2FA no seu projeto Laravel 12 com Breeze e Fortify. O sistema agora está pronto para oferecer uma camada extra de segurança aos usuários, com interface clara, rotas organizadas e controle total sobre o processo de autenticação em dois fatores.
🎨 Passo Extra 1: Melhorar a UX
Para garantir uma experiência mais fluida e consistente no uso da aplicação, também iremos realizar uma pequena melhoria na tela de confirmação de senha (Confirm Password). Por padrão, essa tela é exibida em um layout isolado, que ocupa toda a área da janela, sem cabeçalho ou menu. Isso quebra a harmonia visual da interface, fazendo com que o usuário tenha a impressão de estar fora do sistema. Por isso, altere:
resources\views\auth\confirm-password.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Confirm Password') }}
</h2>
</x-slot>
<div class="py-6">
<div class="max-w-md mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<p class="mb-4 text-sm text-gray-600">
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
</p>
<form method="POST" action="{{ route('password.confirm') }}">
@csrf
<!-- Password -->
<div class="mb-4">
<label for="password" class="block font-medium text-sm text-gray-700">
{{ __('Password') }}
</label>
<input id="password"
type="password"
name="password"
required
autocomplete="current-password"
class="mt-1 block max-w-xs rounded-md shadow-sm border-gray-300
focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50" />
@error('password')
<span class="text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
<div class="flex justify-start">
<x-primary-button>
{{ __('Confirm') }}
</x-primary-button>
</div>
</form>
</div>
</div>
</div>
</x-app-layout>
🎨 Passo Extra 2: Colocar o 2FA no Menu de Usuário
Com a rota profile.two-factor configurada e funcionando como uma página independente, o próximo passo é torná-la acessível para o usuário. O local mais intuitivo é no menu de usuário do Breeze, geralmente encontrado no canto superior direito após o login.
1. Localize o arquivo de navegação: Abra o arquivo resources/views/layouts/navigation.blade.php. Este arquivo é responsável por renderizar a barra de navegação principal, incluindo o menu drop-down do usuário autenticado.
2. Encontre a seção do menu drop-down do usuário: Dentro de navigation.blade.php, localize o bloco de código que define os links do menu drop-down do usuário. Ele geralmente contém o link “Profile” e o formulário de “Log Out”. O código deve ser semelhante a este:
<x-slot name="content">
<x-dropdown-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-dropdown-link>
....
3. Adicione o link para 2FA: Insira um novo x-dropdown-link para a rota profile.two-factor onde você achar mais adequado dentro desse menu, idealmente abaixo do link “Profile”:
<x-dropdown-link :href="route('profile.two-factor')">
{{ __('2FA') }}
</x-dropdown-link>
Após realizar essa modificação e salvar o arquivo, recarregue seu aplicativo no navegador. Ao abrir o menu do usuário (geralmente clicando no nome do usuário logado), você verá a nova opção “2FA” que direcionará o usuário para a página de gerenciamento de 2FA.
🔐 Passo Extra 3: Como Funciona o Processo do 2FA
- O usuário ativa o 2FA na tela de perfil.
- Um QR Code é gerado.
- O QR Code é lido por um app autenticador (Google Authenticator, Microsoft Authenticator, Authy, etc.).
- Na próxima tentativa de login, após informar e-mail e senha, o usuário é direcionado para a tela do desafio do 2FA (
resources/views/auth/two-factor-challenge.blade.php). - Nessa tela, ele informa o código gerado no aplicativo autenticador.
Se não tiver acesso ao app, pode usar um dos códigos de recuperação.
📜 Observação Sobre o Frontend
- O Fortify não oferece frontend nativo, por isso construímos manualmente a interface com Blade + Tailwind CSS + Alpine.js. (O Fortify é um backend de autenticação puro. Ele fornece a lógica de autenticação via contratos e ações, mas a interface do usuário (views) é de responsabilidade do desenvolvedor. A integração com Blade, Tailwind CSS e Alpine.js é a forma comum de fazer isso, especialmente com o Breeze.)
- Não há necessidade de rodar comandos como
npm installespecificamente para o Fortify, exceto se desejar estilizar as novas views ou alterar arquivos CSS/JS existentes. (npm installé para dependências de frontend (JavaScript, CSS, frameworks como React/Vue, ou utilitários como Tailwind CSS). O Fortify em si é uma dependência PHP e não tem suas próprias dependências de frontend que exigiriamnpm install. Você só precisaria donpm installenpm run devse estivesse usando o Breeze (que inclui o Tailwind CSS e Alpine.js) e alterando os arquivos CSS/JS do seu projeto. As views que você criou para o Fortify usam esses assets, então se você alterar as views ou estilos, precisará recompilar os assets.)
🎯 Conclusão
Agora nossa aplicação possui uma autenticação robusta, protegida por 2FA (autenticação em dois fatores), elevando consideravelmente o nível de segurança do sistema.
Na Parte 5, vamos avançar para a construção do Dashboard e do CRUD de tarefas, implementando as funcionalidades principais do nosso Gerenciador de Tarefas.
🔗 Leitura recomendada: Desenvolvimento Web com Laravel, Breeze, Fortify e Tailwind — Parte 3: Apresentando o Projeto Exemplo.