Adicionar autenticação com Laravel Fortify e Vue

Introdução

A autenticação é uma funcionalidade essencial em qualquer sistema, principalmente sistemas web. Ela não só garante o acesso e a guarda de informações pessoais, como ela garante que somente aqueles que tenham autorização consigam aceder às áreas protegidas da aplicação. Promove segurança e privacidade.

Requisitos

Neste artigo vou explorar como instalar a autenticação em uma aplicação Laravel, utilizando o Laravel Fortify. Como base, vou utilizar uma instalação do Laravel com Inertia e Vue. Caso queira saber mais sobre a aplicação base, consulte o artigo Laravel com Inertia e Vue.

Este artigo também assume que as rotas estejam configuradas através do pacote Ziggy, para uso da função route() no Vue. Caso a sua aplicação não tenha as rotas configuradas, consulte o artigo Utilizar rotas do Laravel no Vue.

Por fim, a sua aplicação poderá ter as views de autenticação que quiser. Se quiser também criar as views, acompanhe as views criadas no final deste artigo. Elas usa a framework Bootstrap CSS.

Quais são os requisitos de um autenticação?

Para se implementar uma autenticação, devemos pensar em:

  • Se o utilizador anónimo poderá ou não se registar na aplicação.
  • De que forma o utilizador poderá se autenticar e como validamos a sua identidade.
  • O utilizador poderá ter esquecido a sua password, e neste caso deverá poder recuperá-la de forma segura.

Essas são algumas das considerações que temos que ter antes de iniciar.

Porque o Laravel Fortify?

O primeiro motivo da escolha do Laravel Fortify é que ele é um pacote oficial do Laravel que não impõe nenhuma interface (frontend). Enquanto existem pacotes “starter kits“, em Laravel, que já tem uma interface própria e vão além da autenticação, o Fortify faz todo o trabalho da autenticação sem se preocupar com o layout.

Principais Recursos do Fortify

  1. Registo de utilizadores: Fornece as rotas para criação de novas contas no site.
  2. Login e Logout: Faz a gestão da sessão do utilizador com segurança.
  3. Recuperação de passwords: Implementa solicitações e redefinições de password.
  4. Verificação de E-mail: Garante que o e-mail do utilizador seja autêntico.
  5. Autenticação de Dois Fatores (2FA): Adiciona uma camada extra de segurança.
  6. Atualização de Informações do Perfil: Permite aos utilizadores alterarem dados como password e e-mail.
  7. Confirmação de password: Protege ações sensíveis.

Instalação e configuração do Laravel Fortify

Sem mais delongas, vamos por a mão na massa.

Passo 1: Instale o Laravel Fortify através do composer:

composer require laravel/fortify

Passo 2: Publique o ficheiro de configuração:

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

Passo 3: Registe o Fortify nos Service Providers. Abra o ficheiro \bootstrap\providers.php:

return [
    // ...
    App\Providers\FortifyServiceProvider::class,
];

Passo 4: Configure os serviços que estarão disponíveis em \config\fortify.php. Os serviços que não serão utilizados pela sua aplicação, comente-os:

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

Passo 5: Crie a ligação às views, onde está o frontend da sua aplicação, em app/Providers/FortifyServiceProvider.php:

(...)
use Inertia\Inertia;
(...)
    public function boot(): void
    {
        Fortify::createUsersUsing(CreateNewUser::class);
        Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
        Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);

        RateLimiter::for('login', function (Request $request) {
            $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());

            return Limit::perMinute(5)->by($throttleKey);
        });

        RateLimiter::for('two-factor', function (Request $request) {
            return Limit::perMinute(5)->by($request->session()->get('login.id'));
        });
        
        Fortify::loginView(function () {
            return Inertia::render('Auth/Login');
        });

        Fortify::registerView(function () {
            return Inertia::render('Auth/Register');
        });

        Fortify::requestPasswordResetLinkView(function () {
            return Inertia::render('Auth/ForgotPassword');
        });

        Fortify::resetPasswordView(function (Request $request) {
            return Inertia::render('Auth/ResetPassword', [
                'token' => $request->token,
                'email' => $request->email,
            ]);
        });        
    }

Se a sua aplicação já tiver as views de autenticação definidas, altere em conformidade o código acima, e a seguir pule o próximo passo. Caso não tenha, siga no próximo passo a criação das views.

Passo 6: Definir a rota principal

Com o Laravel Fortify é possível definir a rota para onde serão reencaminhados os utilizadores registados. No ficheiro \config\fortify.php, configure a rota principal em:

    /*
    |--------------------------------------------------------------------------
    | Home Path
    |--------------------------------------------------------------------------
    |
    | Here you may configure the path where users will get redirected during
    | authentication or password reset when the operations are successful
    | and the user is authenticated. You are free to change this value.
    |
    */

    'home' => '/dashboard',

Passo 7: Informações do utilizador para o Vue

Para passar as informações do utilizador logado para o Vue, utiliza-se do Middleware do Inertia. Assim, altere o ficheiro \app\Http\Middleware\HandleInertiaRequests.php:

    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            // Share user data globally
            'auth' => [
                'user' => $request->user() ? [
                    'id' => $request->user()->id,
                    'name' => $request->user()->name,
                    'email' => $request->user()->email,
                    // Add any other user data you need
                ] : null,
            ],
            'flash' => [
                'success' => fn () => $request->session()->get('success'),
                'warning' => fn () => $request->session()->get('warning'),
                'info' => fn () => $request->session()->get('info'),
                'error' => fn () => $request->session()->get('error'),
                'place' => fn () => $request->session()->get('place'),
            ]
        ]);
    }

Criação das views de Autenticação

Login

Crie o componente de Login, em resources/js/Pages/Auth/Login.vue:

<script setup>
import { Link, useForm } from '@inertiajs/vue3'

    const form = useForm({
      email: '',
      password: '',
      remember: false,
    })

    const submit = () => {
      form.post(route('login'))
    }
</script>

<template>
  <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center bg-light">
    <div class="container">
      <div class="row justify-content-center">
        <div class="col-12 col-sm-8 col-md-6 col-lg-4">
          <div class="card shadow">
            <div class="card-body">
              <h1 class="h3 mb-4 text-center text-secondary">Login</h1>

              <form @submit.prevent="submit">
                <div class="mb-3">
                  <label class="form-label" for="email">
                    Email
                  </label>
                  <input
                    id="email"
                    type="email"
                    v-model="form.email"
                    class="form-control"
                    required
                  />
                  <div v-if="form.errors.email" class="text-danger small mt-1">{{ form.errors.email }}</div>
                </div>

                <div class="mb-3">
                  <label class="form-label" for="password">
                    Senha
                  </label>
                  <input
                    id="password"
                    type="password"
                    v-model="form.password"
                    class="form-control"
                    required
                  />
                  <div v-if="form.errors.password" class="text-danger small mt-1">{{ form.errors.password }}</div>
                </div>

                <div class="d-flex justify-content-between align-items-center mb-3">
                  <div class="form-check">
                    <input type="checkbox" v-model="form.remember" class="form-check-input" id="remember">
                    <label class="form-check-label small" for="remember">Lembrar-me</label>
                  </div>
                  <Link :href="route('password.request')" class="small text-primary text-decoration-none">
                    Esqueceu sua senha?
                  </Link>
                </div>

                <button
                  type="submit"
                  class="btn btn-primary w-100"
                  :disabled="form.processing"
                >
                  {{ form.processing ? 'Entrando...' : 'Entrar' }}
                </button>
              </form>

              <div class="text-center mt-4">
                <Link :href="route('register')" class="small text-secondary text-decoration-none">
                  Não tem uma conta? Registe-se
                </Link>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

Registo

Crie o componente de Registo, em resources/js/Pages/Auth/Register.vue:

<script setup>
import { Link, useForm } from '@inertiajs/vue3'

const form = useForm({
  name: '',
  email: '',
  password: '',
  password_confirmation: ''
})

const submit = () => {
  form.post(route('register'))
}
</script>

<template>
  <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center bg-light">
    <div class="container">
      <div class="row justify-content-center">
        <div class="col-12 col-sm-8 col-md-6 col-lg-4">
          <div class="card shadow">
            <div class="card-body">
              <h1 class="h3 mb-4 text-center text-secondary">Registrar</h1>

              <form @submit.prevent="submit">
                <div class="mb-3">
                  <label class="form-label" for="name">
                    Nome
                  </label>
                  <input
                    id="name"
                    type="text"
                    v-model="form.name"
                    class="form-control"
                    required
                  />
                  <div v-if="form.errors.name" class="text-danger small mt-1">{{ form.errors.name }}</div>
                </div>

                <div class="mb-3">
                  <label class="form-label" for="email">
                    Email
                  </label>
                  <input
                    id="email"
                    type="email"
                    v-model="form.email"
                    class="form-control"
                    required
                  />
                  <div v-if="form.errors.email" class="text-danger small mt-1">{{ form.errors.email }}</div>
                </div>

                <div class="mb-3">
                  <label class="form-label" for="password">
                    Senha
                  </label>
                  <input
                    id="password"
                    type="password"
                    v-model="form.password"
                    class="form-control"
                    required
                  />
                  <div v-if="form.errors.password" class="text-danger small mt-1">{{ form.errors.password }}</div>
                </div>

                <div class="mb-4">
                  <label class="form-label" for="password_confirmation">
                    Confirmar Senha
                  </label>
                  <input
                    id="password_confirmation"
                    type="password"
                    v-model="form.password_confirmation"
                    class="form-control"
                    required
                  />
                </div>

                <button
                  type="submit"
                  class="btn btn-primary w-100"
                  :disabled="form.processing"
                >
                  {{ form.processing ? 'Registrando...' : 'Registrar' }}
                </button>
              </form>

              <div class="text-center mt-4">
                <Link :href="route('login')" class="small text-secondary text-decoration-none">
                  Já tem uma conta? Faça login
                </Link>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

Proteja as rotas privadas

O middleware auth protege as rotas que exigem que o utilizador esteja autenticado. Altere o ficheiro \routes\web.php para proteger as rotas:

// routes/web.php
use Inertia\Inertia;
(...)
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
    Route::get('/dashboard', function () {
        return Inertia::render('Dashboard');
    })->name('dashboard');
});

Espero que o artigo lhe seja útil. Qualquer questão, escreva nos comentários.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *