Capa de post contendo o logo oficial do Laravel Fortify

Introdução ao Laravel Fortify


novidades jetstream laravel-8 fortify

No post anterior começamos a alterar as views de autenticação do Jetstream mas ainda não vimos como alterar as regras no backend. É exatamente isso que vamos aprender neste post. Então vamos conhecer o Laravel Fortify!

O Laravel Fortify ganhou vida em agosto de 2020 para se tornar parte de algo maior! O Fortify é um backend agnóstico a frontend e nasceu para prover funcionalidades como autenticação, cadastro de usuários, verificação por email e autenticação em dois fatores para o Laravel Jetstream.

TL;DR

Se você estiver acompanhando a série sobre o Jetstream e quiser verificar o código utilizado neste post basta acessar o repositório no github e utilizar o branch autenticacao-jetstream, cada alteração feita durante os posts estão em um commit próprio para deixar mais fácil a visualização das alterações =)

O que é o Laravel Fortify?

Apesar de ter nascido para prover funcionalidades de backend para o Laravel Jetstream, o Laravel Fortify é agnóstico a frontend, e isso inclui ser agnóstico ao próprio Jetstream. Com ele temos um arcabouço de funcionalidades bem completo e isso pode nos poupar bastante tempo na construção de novas aplicações.

Funcionalidades disponíveis

Conforme já citado, o Fortify já traz prontas algumas funcionalidades interessantes referentes a autenticação e gerenciamento de usuário. Quando instalado junto com Jetstream um arquivo de configurações é criado em config/fortify.php e mais precisamente no array features podemos identificar quais funcionalidades estão disponíveis para serem utilizadas.

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    // Features::emailVerification(),
    Features::updateProfileInformation(),
    Features::updatePasswords(),
    Features::twoFactorAuthentication([
        'confirmPassword' => true,
    ]),
],

Os nomes são bem sugestivos e como podemos ver a funcionalidade de verificação por email vem desabilitada por padrão. Isto porque para utilizá-la algumas configurações são necessárias, como por exemplo configurar um servidor smtp ou utilizar algum já configurado.

Diferença entre usar o Fortify com ou sem o Jetstream

O Laravel Fortify nasceu para ser um conjunto de funcionalidades agnóstico a um frontend, porém, desde sua concepção a ideia era para que fosse utilizado junto ao Jetstream. Por conta disso existem algumas diferenças quanto às funcionalidades quando o Fortify é utilizado sem o Jetstream.

Caso queira utilizar o Fortify sem estar em conjunto com o Jetstream, o próprio Laravel recomenda que apenas algumas funcionalidades sejam habilitadas, são elas registro, reset de senhas e verificação por email. Desta forma o arquivo de configurações “deveria” conter as seguintes funcionalidades:

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    //Features::emailVerification(),
],

É claro que isto é apenas uma recomendação, caso seja interessante nada nos impede de criarmos um frontend e regras para utilizar as demais funcionalidades.

Actions

Agora que já sabemos quem é esse tal de Fortify podemos dar continuidade com nossa série!

Em postagens anteriores cheguei a comentar sobre as actions mas não foi mostrado nenhum conteúdo referente a elas, vamos entender do que se trata e, obviamente, modificá-las.

As actions são classes onde serão executadas as lógicas referentes às requisições de autenticação. Todas as actions criadas na instalação residem no diretório app/Actions e estão separadas nas pastas Fortify e Jetstream, segue a representação da estrutura de pastas:

Actions/
├── Fortify
│   ├── CreateNewUser.php
│   ├── PasswordValidationRules.php
│   ├── ResetUserPassword.php
│   ├── UpdateUserPassword.php
│   └── UpdateUserProfileInformation.php
└── Jetstream
    ├── AddTeamMember.php
    ├── CreateTeam.php
    ├── DeleteTeam.php
    ├── DeleteUser.php
    └── UpdateTeamName.php

O mais interessante é que em cada uma das classes uma interface específica é implementada, ou seja, caso seja necessário alterar totalmente a classe ou até mesmo utilizar uma classe diferente para as actions, basta que a mesma interface seja implementada.

Mais pra frente vamos mexer nessas classes e alterar alguns comportamentos mas antes vamos entender algumas configurações do Laravel Fortify.

Configurações

Quase todo pacote oficial do Laravel é seguido de um arquivo de configurações, isso torna muito fácil alterar comportamentos de maneira conveniente e rápida. O arquivo de configurações do Fortify reside em config/fortify.php, vamos explorar um pouco este arquivo e entender algumas configurações.

Alterando rotas de redirecionamento

Ao executar algumas ações é comum que o usuário precise ser redirecionado para alguma rota específica, por exemplo: para qual rota o usuário deve ser redirecionado após efetuar o login na plataforma?

No arquivo config/fortify.php podemos alterar esta configuração de redirecionamento na chave home do array de configurações. Por padrão está sendo utilizada a constante HOME do RouteServiceProvider a qual leva o usuário que foi autenticado com sucesso para a rota /dashboard.

// config/fortify.php
'home' => RouteServiceProvider::HOME,

Na nossa aplicação, quando o usuário for autenticado com sucesso, ao invés de acessar o dashboard queremos que ele acesse a página de gerenciamento de perfil e esta página se encontra na rota /user/profile. Alterar este comportamento é muito simples e pode ser feito de duas maneiras: alterando a constante HOME na classe RouteServiceProvider, método este que não recomendo, ou podemos alterar diretamente no arquivo de configuração e é isso que vamos fazer. Vamos alterar o arquivo de configuração para redirecionar nosso usuário logado com sucesso para a rota /user/profile.

// config/fortify.php
'home' => '/user/profile',

Basta salvar o arquivo com a alteração e efetuar um login. Agora toda vez que um usuário for autenticado com sucesso será direcionado para a página de gerenciamento de perfil.

Página de gerenciamento de perfil do usuário após efetuar ação de login

Limite de requisições

Uma funcionalidade bem bacana que já é feita automaticamente pelo Fortify é um controle de quantas requisições poderão ser feitas por um usuário dentro do prazo de 1 minuto na rota de login. Por padrão são utilizadas 5 requisições e a regra é: quantas requisições um mesmo email no mesmo endereço IP foram executadas. A configuração desta funcionalidade também é encontrada no arquivo config/fortify.php.

// config/fortify.php
'limiters' => [
    'login' => null,
],

Vamos alterar este limite para 3

// config/fortify.php
'limiters' => [
    'login' => 3,
],

Após salvar as alterações vamos tentar logar com credenciais incorretas por 4 vezes seguidas, o resultado será algo parecido com este:

Página de erro contendo a informação que muitas tentativas de requisição foram efetuadas

Outras configurações como fazer login por usuário + senha ao invés de email + senha, qual middleware a rota irá utilizar (web, api ou qualquer outro) e quais funcionalidades estão habilitadas/desabilitadas também podem ser encontradas neste arquivo. Recomendo explorar algumas e fazer suas próprias alterações para entender melhor cada uma.

Desabilitando funcionalidades do Fortify

Vimos que no array de configurações do Fortify existe a chave features e seus valores representam as funcionalidades que estão habilitadas. Caso seja interessante habilitar ou desabilitar alguma funcionalidade, basta adicionar ou remover algum dos valores.

Por padrão a verificação por email vem desabilitada, podemos enxergar este comportamento observando as configurações e podemos notar que a funcionalidade //Features::emailVerification(), está comentada. Para habilitá-la bastaria descomentar esta linha. Para desabilitar outras funcionalidades basta comentar outras linhas ou removê-las do array. Além da verificação por email, nossa aplicação não vai precisar utilizar autenticação em dois fatores mas esta funcionalidade vem habilitada por padrão na instalação do Laravel Jetstream. Na página de gerenciamento de perfil /user/profile podemos verificar esta funcionalidade:

Exemplo de página de gerenciamento de perfil contendo o componente de autenticação em dois fatores

Como não precisamos de autenticação em dois fatores, vamos comentar a linha desta funcionalidade no arquivo de configurações em config/fortify.php para desabilitá-la

// config/fortify.php
'features' => [
    Features::registration(),
    Features::resetPasswords(),
    // Features::emailVerification(),
    Features::updateProfileInformation(),
    Features::updatePasswords(),
    // Features::twoFactorAuthentication([
    //     'confirmPassword' => true,
    // ]),
],

Agora vamos atualizar a página de gerenciamento de perfil, podemos perceber que a sessão de autenticação em dois fatores do Laravel Jetstream não está mais lá:

Exemplo de página de gerenciamento de perfil com o componente de autenticação em dois fatores removido

Apesar do fato de que removemos apenas a configuração de funcionalidades no Fortify, tanto o frontend quanto o backend não serão mais utilizados. Mas não se preocupe, podemos habilitar e desabilitar funcionalidades a qualquer momento

Autenticação com Laravel Fortify + Jetstream

Além de habilitar e desabilitar funcionalidades, também temos a flexibilidade para modificar o comportamento de algumas funcionalidades, por exemplo, podemos alterar as regras de autenticação de um usuário.

Na classe Laravel\Fortify\Fortify temos alguns métodos que são utilizados quando algumas ações são realizadas. No exemplo da autenticação de usuário, o método authenticateUsing() é o método executado para atender as regras de autenticação.

Utilizando o service provider do Fortify, FortifyServiceProvider, no método boot, podemos alterar esses comportamentos. Os métodos da classe Fortify recebem um callback que será executado para validar alguma regra. Desta forma se torna muito fácil alterar uma ação através do service provider.

Como customizar a autenticação de usuários

Sabemos que durante o processo de autenticação de usuários o Laravel Fortify verifica email e senha para validar as credenciais cadastradas. Este processo é feito através de um algoritmo mais ou menos assim:

$user = User::where('email', $request->email)->first();

if ($user &&
    Hash::check($request->password, $user->password)) {
    return $user;
}

No código acima basicamente é verificado se existe um usuário cadastrado com o email digitado no formulário e se a senha digitada no formulário para este usuário bate com a senha cadastrada.

Na nossa aplicação queremos que além de verificar usuário e senha, que o algoritmo verifique se o usuário está ativo ou inativo em nossa base de dados, consultando a coluna status na tabela users.

Antes de mais nada precisamos criar esta coluna no banco de dados, vamos fazer isso utilizando as migrations, vamos criar uma migration utilizando o comando artisan:

php artisan make:migration alter_users_table_add_column_status

Created Migration: 2020_10_02_155446_alter_users_table_add_column_status

Em seguida vamos alterar a tabela users, adicionar a coluna status após a coluna password e vamos dar o valor padrão para esta coluna como active, nossa migration fica assim:

class AlterUsersTableAddColumnStatus extends Migration
{
    /**
     * Run the migrations.
     *
     * @return  void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('status')
                ->after('password')
                ->default('active');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return  void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('status');
        });
    }
}

Em seguida vamos executar a migration para que a coluna seja criada no nosso banco de dados:

php artisan migrate

Migrated:  2020_10_02_155446_alter_users_table_add_column_status (57.13ms)

Desta forma os usuários já existentes na base de dados vão receber o status ‘active’ e novos usuários serão criados já com status ‘active’ também, a não ser que outro status seja passado no momento de sua criação.

Agora sim podemos alterar nossa lógica de autenticação de usuários! Vamos adicionar a chamada ao método authenticateUsing() ao método boot da classe FortifyServiceProvider e adicionar nossa regra:

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
// ...
    /**
     * Bootstrap any application services.
     *
     * @return  void
     */
    public function boot()
    {
        Fortify::authenticateUsing(function (Request $request) {
            $user = User::where('email', $request->email)
                ->where('status', 'active')
                ->first();

            if (
                $user &&
                Hash::check($request->password, $user->password)
            ) {
                return $user;
            }
        });
       // ...
    }
}

Pronto! Agora apenas usuários com email + senha válidos e que estejam com o status ‘active’ poderão acessar a área logada do sistema. Você pode fazer um teste alterando o status de algum usuário para algo diferente de ‘active’ e tentando fazer login no sistema =)

Cadastro de usuários

No post anterior fizemos uma alteração no formulário de cadastro de usuários. Alteramos o formulário para receber name e surname ao invés de apenas um campo. O problema é que o como não mexemos no backend este recurso não surtiu efeito no cadastro de usuários, pois apesar dos dados estarem sendo enviados corretamente na requisição nós não fizemos nenhum tratamento para a persistência.

Vamos alterar a action do Laravel Fortify responsável pela criação de usuários e adicionar uma regra para unificar os campos name e surname então.

Se olharmos novamente para o FortifyServiceProvider podemos visualizar que no método boot está registrada a classe que cuidara do cadastro de usuários no método

Fortify::createUsersUsing(CreateNewUser::class);

Aqui, assim como nos demais métodos, podemos alterar o comportamento de duas maneiras: 1) alterando a classe existente, neste caso a classe CreateNewUser; 2) Criando uma classe totalmente nova que respeite a interface.

Alterando o cadastro de usuários

Por conveniência vamos alterar a classe atual ao invés de criar uma nova. Vamos adicionar uma validação para o campo surname e criar uma regra para unificar os inputs surname e name em um só.

Vamos primeiro criar a validação. Queremos que o campo surname seja obrigatório, seja uma string e contenha no mínimo 3 caracteres. Vamos adicionar esta regra a instancia do validator utilizada na classe App\Actions\Fortify\CreateNewUser.

// app/Actions/Fortify/CreateNewUser
Validator::make($input, [
    'name' => ['required', 'string', 'max:255'],
    'surname' => ['required', 'string', 'min:3'], // aqui veio a adição das regras
    'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
    'password' => $this->passwordRules(),
])->validate();

Agora ao acessar a página de registros (/register) se não enviarmos o campo surname no formulário de cadastro, teremos um erro de validação:

Exemplo de página de cadastro de usuários contendo um erro de validação por não ter preenchido o campo 'surname'

Obs: Para poder mostrar o erro, foi necessário remover o atributo required do input no arquivo register.blade.php.

Ótimo! Nossa validação está funcionando. Agora vamos criar uma regra para unificar os campos name e surname em um só para podermos efetuar o cadastro do usuário:

// app/Actions/Fortify/CreateNewUser
return DB::transaction(function () use ($input) {
    return tap(User::create([
        'name' => "{$input['name']} {$input['surname']}",
        'email' => $input['email'],
        'password' => Hash::make($input['password']),
    ]), function (User $user) {
        $this->createTeam($user);
    });
});

Agora precisamos preencher o formulário com todos os campos, vamos precisar preencher os campos name e surname desta vez para que nosso backend possa compor o nome completo:

Formulário de cadastro de usuários contendo o campo 'surname'

Ao finalizar o cadastro, somos redirecionados para a página de gestão de perfil, lá poderemos ver que deu tudo certo e o nome completo está sendo exibido no campo name:

Página de gerenciamento de perfil contendo o campo 'name' exibindo o nome e sobrenome concatenados

Como alteramos apenas o formulário de cadastro, no gerenciamento de perfil não temos o campo surname.

Utilizei o formulário de cadastro de usuários apenas como exemplo e por isso não alterei a estrutura do banco de dados e tudo mais. A ideia é mostrar que é fácil customizar as regras nas actions do Laravel Fortify. No post anterior fizemos algumas alterações no frontend, recomendo que dê uma olhada, baixe o repositório com a aplicação de exemplo e altere você mesmo o formulário de gerenciamento de perfil para fixar os conhecimentos adquiridos.

Conclusão

As actions e configurações do Laravel Fortify nos dão bastante liberdade para modificarmos a aplicação para atender nossas regras de maneira fácil e conveniente. O Fortify em conjunto com o Jetstream é um excelente ponto de partida para aplicações Laravel, podemos deixar algumas funcionalidades mais triviais por conta do Laravel e nos preocupar mais com nossas próprias regras, garantindo mais agilidade na validção de produtos ou até mesmo no estudos.

Espero ter ajudado a entender um pouco mais sobre mais esta nova funcionalidade do Laravel 8! Se você curtiu este post ou gostaria de me dar algum feedback fique à vontade pra me pingar no Twitter .

Para não perder as próximas publicações você pode utilizar o feed json ou o feed atom em seu agregador favorito, desta forma você receberá uma notificação a cada post novo!

-- Up the Laravel's \o/

← Post Anterior
Próximo Post →