Skip to content ↓

Introducing laravel-tfa-confirmation

• 4 min read

Earlier today I tagged v1 of a new Laravel package. laravel-tfa-confirmation – as the name might suggest – the package has to do with two-factor-authentication.

Let me explain what the package does and how it could be beneficial for your project.

What problem does the package solve? #

The package was born from my work a Trenda in 2024.
In the app, users can enable 2FA for their account and we wanted to force users to enter their 2FA code if they visited certain sections of the app (eg. the team settings page).

However, they shouldn't have to enter their 2FA code everytime they visited the page. Only in a given time period.

This is basically the same behaviour as the "Password Confirmation" feature in Laravel, but for 2FA codes. It's also similar to GitHub's sudo mode feature.

How does it work? #

After installing the package through composer, you get a new RequireTwoFactorAuthenticationConfirmation-middleware, which you can apply to a single route or an entire route group.

Based on a configurable timeout, your users is either forced to enter their 2FA code or is being redirected to the intended route. (Probably important to note, that the package depends on laravel/fortify and that you have to use Laravel Fortify as your 2FA "provider".)

The package provides the routes and controllers to ask for a 2FA code and to verify that the entered 2FA code is valid. The "validation" is then stored in the session for a configured time; thus preventing the user from being asked a 2FA code again.

The package actually listens to the Laravel\Fortify\Events\ValidTwoFactorAuthenticationCodeProvided -event fired by Fortify. This has the added benefit, that users don't have to enter their 2FA code again, if they just have logged in and confirmed their 2FA code. This streamlines the user experience, if for example you apply the middleware to your admin-panel. Users then don't have to enter a 2FA code twice, if they just logged in.

What the package doesn't provide #

The default challenge view presented to users to confirm their 2FA code is not styled at all.

Screenshot of the default challenge view presented to users.
The default challenge view shipped with the package is not styled at all. You as a developer have to apply the styles on your own.

I've thought as the the design of each app differs anyway and there is no default-style for Laravel apps, it does not make sense to ship styled HTML. [1]

If you use this package, you have to publish the view and update the template accordingly.

Outlook #

I'm using v1 of the package already in my side projects to protect my Filament admin panels with a 2FA code.

In my biased view, I think v1 of the package works great and I encourage you – dear reader – to give it a try in your project.

Nevertheless, I have ideas for future improvements to the package, but I just haven't nailed the API yet:

Custom session lengths for certain actions #

Right now the package has 1 timeout configuration. If, for example, you want that users have to enter a 2FA code every 24 hours to access an admin panel, but every 10 minutes if they access their billing settings, this doesn't work yet.

There is just one challenge-view and the "challenge confirmation" is stored in the session through a detached event listener. The code in the event listener currently doesn't know, for which challenge a confirmation should be stored.

I think I would have to add some sort of hidden "name"-field or "name"-session value to make this all work.

ConfirmsTwoFactor Contract #

Already in the code base, but not used yet is a ConfirmsTwoFactor contract. My idea is to add this to a model like User or Team.

In userland, developers could then write code to determine if a challenge should be shown and or how long the confirmations lasts. For example, a team could enable a setting that forces users to confirm a code every 15 minutes for all their users; even when the default value would be every 24 hours.


As you can see, I've got some ideas for this package. For now, I'm happy with v1 and will tinker with a neat API for the other features.
If you have any feedback, please reach out!


  1. If you think this is a bad idea or have a great idea for a default design, feel free to send in a pull request ↩︎