Filament - How to force renew password on first user login

Julien BOYER
Julien BOYER (aka. yebor974) Buy Me A Coffee
Published at 03 Jul 2024
This article describes how we can force a newly created user by an admin to renew their password on the first login.

This article uses the Filament Renew Password Plugin.

  1. Start by installing and registering the plugin on panel.
composer require yebor974/filament-renew-password

We publish default plugin migration that will add two columns to users table : last_password_renew_at and force_renew_password

php artisan vendor:publish --tag="filament-renew-password-migrations"
php artisan migrate

We register the plugin to panel and add force renewal process and timestamp management.

use Yebor974\Filament\RenewPassword\RenewPasswordPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(
            (new RenewPasswordPlugin())
                ->forceRenewPassword() // activate the force renewal process
                ->timestampColumn() // activate last_password_renew_at column, updating it with each password renewal.
        )
    );
}

We implement RenewPasswordContract on User Model, add default RenewPassword trait and declare fillable attributes.

We will just declare a name and email attributes for user.

use Illuminate\Foundation\Auth\User as Authenticatable;
use Yebor974\Filament\RenewPassword\Contracts\RenewPasswordContract;
use Yebor974\Filament\RenewPassword\Traits\RenewPassword;
//...

class User extends Authenticatable implements RenewPasswordContract
{
    use RenewPassword;
	
    protected $fillable = [
        'name',
        'email',
        'force_renew_password'
    ];
	
    //...
}

If we set true to force_renew_password attribute,the user will be automatically redirect to the renewal password process.

  1. Create User Resource
php artisan make:filament-resource User
class UserResource extends Resource
{
    protected static ?string $model = User::class;

    protected static ?string $navigationIcon = 'heroicon-o-users';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\Section::make()
                    ->schema([
                        TextInput::make('name')
                            ->required(),
                        TextInput::make('email')
                            ->required()
                            ->unique(ignoreRecord: true)
                    ])->columns(2)
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('name')
                    ->searchable(),
                Tables\Columns\TextColumn::make('email')
                    ->searchable(),
                Tables\Columns\IconColumn::make('force_renew_password')
                    ->boolean()
            ])
            ->filters([
                //
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }
	
		//...
}

Now, we need to generate default password and invite user to connect and renew password. On CreateUser.php page we have to override the mutateFormDataBeforeCreate and handleRecordCreation functions like that:

class CreateUser extends CreateRecord
{
    protected static string $resource = UserResource::class;

    protected string $password;

    protected function mutateFormDataBeforeCreate(array $data): array
    {
        $this->password = Str::password(12); // generate a default password with length of 12 caracters
        $data['password'] = bcrypt($this->password);
        $data['force_renew_password'] = true; // to force user to renew password on next login

        return $data;
    }

    protected function handleRecordCreation(array $data): Model
    {
        /** @var User $user */
        $user = parent::handleRecordCreation($data); // handle the creation of the new user

        $user->notify(new NewAccount($this->password)); // notify the new user with account details

        return $user;
    }
}

The NewAccount notification class is:

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\HtmlString;

class NewAccount extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     */
    public function __construct(protected string $password, protected ?Model $tenant = null)
    {
        $this->afterCommit();
    }

    /**
     * Get the notification's delivery channels.
     *
     * @return array<int, string>
     */
    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     */
    public function toMail(object $notifiable): MailMessage
    {
        $appName = config('app.name');

        return (new MailMessage)
            ->subject("Your account has been created on $appName")
            ->line("Here are your login details:")
            ->line(new HtmlString("<strong>Email</strong> : {$notifiable->email}"))
            ->line(new HtmlString("<strong>Temporary password</strong> : {$this->password}"))
            ->line("You will be prompted to change this temporary password at your next login.")
            ->action('Go to app', filament()->getUrl($this->tenant));
    }

    /**
     * Get the array representation of the notification.
     *
     * @return array<string, mixed>
     */
    public function toArray(object $notifiable): array
    {
        return [
            //
        ];
    }
}

That's all!

  1. We can test our code
  • Create user

  • Receive notification (with smtp mailpit mailer)

  • Login and renew passord

Other posts

Plugins

To contact me

If you have an idea to bring to life, a problem to solve, or a significant technical challenge to tackle, don't waste any more time. Contact me now and let me help you turn your ambitions into reality. My expertise is at your disposal to successfully materialize your projects.

Geographic location

Paris & Reunion Island (France - UTC+4)