A Guide to Override the default Login behavior in FilamentPHP

Introduction

The default login class that comes with Filament is sufficient for most cases since it contains the email and password field along with a remember_me checkbox.

However, there are some cases where you must override the default behavior and implement features accordingly. So, in this article, we'll discuss how we can override the default login class and implement our own features on top of it.


Configuration

Before working on the Custom Login Class, we first need to know how FilamentPhp handles the login flow under the hood. To know that, we'll start by looking into the default Service Provider that comes with FilamentPHP which is the AdminPanelProvider which lies inside app/Providers/Filament/ namespace.

If we look into that class, there's a login method chained to the $panel, which indicates that the login feature should be enabled for this panel.

If we go ahead and look into the implementation of that method, we'll see something like this:

/**
     * @param  string | Closure | array<class-string, string> | null  $action
     */
    public function login(string | Closure | array | null $action = Login::class): static
    {
        $this->loginRouteAction = $action;

        return $this;
    }

So, by default, if we don't pass any class to this login() method, the default Login::class is used.

So, the general idea here is to extend this class, make necessary changes to that class as per our needs, and pass that class to the login() method.

Defining a Custom Login Class

So, let's start by defining a custom login class by extending from the default login class. We'll define this inside app/Filament/Pages namespace and name it CustomLogin.

The CustomLogin::class looks like this for now:

<?php

namespace App\Filament\Pages;

use Filament\Http\Livewire\Auth\Login;

class CustomLogin extends Login
{
    //
}

The default Login class includes everything related to login, like

  • static view property (which we can override if we want to pass our own view)

  • the authenticate method (which we can override if we want to update how the user is authenticated)

  • getTitle and getHeading methods, to override the title and headings of the page

and other similar properties and methods which be overridden, based on our needs.

Authenticating with Username instead of Email

As an example, we'll be overriding the default behavior and authenticate with a username instead of the default email.

There are multiple ways to do this, we could override the getForms method and pass a $this->getUsernameFormComponent() method instead of $this->getEmailFormComponent(),

and on that getUsernameFormComponent() method, we could pass a form component for the username field.

But, we'll keep things simple and just override the getEmailFormComponent() method and pass a form component for the username field.

The getEmailFormComponent() looks like this when we pass a Form component for the Username field

protected function getEmailFormComponent(): Component
    {
        return TextInput::make('username')
            ->label('Username')
            ->required()
            ->autofocus()
            ->extraInputAttributes(['tabindex' => 1])
            ->autocomplete();
    }

We also need to override the getCredentialsFromFormData method since this method is passed to the authenticate method, and replace email key with the username key.

The getCredentialsFromFormData looks like this after the changes:

protected function getCredentialsFromFormData(array $data): array
    {
        return [
            'username' => $data['username'],
            'password' => $data['password'],
        ];
    }

The final login class after our necessary modifications looks like this:

<?php

namespace App\Filament\Pages;

use Filament\Facades\Filament;
use Filament\Pages\Auth\Login;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Illuminate\Contracts\Support\Htmlable;


class CustomLogin extends Login
{
    public function mount(): void
    {
        if (Filament::auth()->check()) {
            redirect()->intended(Filament::getUrl());
        }

        if (app()->environment('local')) {
            $this->form->fill([
                'username' => 'admin',
                'password' => 'password',
            ]);
        }
    }

    protected function getCredentialsFromFormData(array $data): array
    {
        return [
            'username' => $data['username'],
            'password' => $data['password'],
        ];
    }

    public function getTitle(): string|Htmlable
    {
        return __('Admin Login');
    }

    public function getHeading(): string|Htmlable
    {
        return __('Admin Login');
    }

    protected function getEmailFormComponent(): Component
    {
        return TextInput::make('username')
            ->label('Username')
            ->required()
            ->autofocus()
            ->extraInputAttributes(['tabindex' => 1])
            ->autocomplete();
    }
}

Along with the methods discussed above, I've also overridden a few more methods, like the getTitle, getHeading and the mount method. The mount method is a bit interesting because it checks whether the user is authenticated, if it is then it will redirect to the intended dashboard.

If it is not logged in, then we'll fill the form with the default Username and Password if the app's environment is local.


FilamentPHP Course

I guess you've already figured out that I love the TALL stack along with FilamentPHP and I have been exploring it a lot lately, and to further improve your skills and help you get started, I have created a Udemy Course based on this stack, where we'll build multiple practical projects and learn a ton of stuff along the way.

You can check out the Course here