NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Open source

Two-Factor for Laravel

Install
composer require roundly-consulting/two-factor-for-laravel
Requires: PHP ^8.4 · Laravel ^12.0|^13.0

Overview

Native two-factor authentication for Laravel, built on the RFC 6238 TOTP standard every authenticator app speaks. It generates the secret and the otpauth:// provisioning URI, confirms enrolment, verifies login codes with constant-time comparison and atomic replay protection, and issues single-use recovery codes for a lost phone — all through TwoFactor::for($user). Secrets are encrypted at rest, recovery codes hashed, and a per-user brute-force limiter is on by default. MIT-licensed and dependency-light: the TOTP maths comes from our own crypto-for-laravel — no third-party crypto or QR library in the security path.

What you get

Guided enrolment

Start returns the secret, the otpauth:// URI and the recovery codes exactly once; 2FA switches on only after the first code confirms it.

Replay-safe verification

Constant-time comparison and an atomic timestep claim — an intercepted code can’t be used twice, even by two racing requests.

Single-use recovery codes

Hashed by default, consumed under a row lock, and every attempt reports how many codes the user has left.

Built-in brute-force limiter

Five attempts a minute per account by default, each counted before it is verified — with a typed exception, an event and a one-line opt-out.

Encrypted and hidden

The secret is encrypted with your APP_KEY, and the sensitive columns never appear in toArray() or a JSON response.

Standards-compatible

The SHA1, 6-digit, 30-second RFC 6238 profile by default — secrets from another library keep their codes, so nobody re-enrols; the stored columns are re-encrypted once.

Facade, DI or actions

TwoFactor::for($user), the injected TwoFactorService or one action class — and TwoFactor::fake() records every call in your tests.

Documentation

Installation

Install via Composer, publish the migration that adds four nullable columns, and optionally publish the config.

Configuration

Every config/two-factor.php key and default — TOTP parameters, issuer, recovery codes, limiter, replay guard, table and columns.

Model setup

Implement TwoFactorAuthenticatable, use the HasTwoFactorAuthentication trait and spread twoFactorCasts() into your casts.

The TwoFactor facade

One entry point: TwoFactor::for($user) for enrolment, login challenges, status, recovery codes and disabling, plus the flat TOTP primitives.

DI and actions

Inject the TwoFactorService contract instead of calling the facade, or run a single action — the same code path, and the fake still applies.

Enrolment

Start a pending enrolment, show the secret, QR URI and recovery codes once, then confirm it with the first authenticator code.

Rendering the QR code

Turn the otpauth:// provisioning URI into a QR code — server-side as SVG with qr-for-laravel, or client-side in your front end.

Verifying at login

Check a challenge code with TwoFactor::for($user)->attempt() — TOTP with replay protection first, then a single-use recovery code.

Recovery codes

Single-use backup codes for a lost device — generated on enrolment, stored hashed by default, consumed atomically, regenerable.

Brute-force limiter

A per-user throttle counted before every check — five attempts a minute by default — with an exception, an event and an opt-out.

Replay protection

Every accepted code claims its timestep atomically, so an intercepted code can’t be reused — tracked in a column, the cache or your own guard.

Trait verbs and actions

The whole lifecycle as verbs on the user model — sugar over TwoFactor::for($this), so the fake records them and the same actions run.

Events

Nine events across enrolment, verification, recovery codes, replays and lockouts — each carries the user, never a secret or a code.

Exceptions

One TwoFactorException base class and seven typed failures — wrong code, enrolment state, rate limit, bad config and more.

Security model

Encrypted secrets, hashed recovery codes, constant-time checks, atomic replay and recovery-code guards, and bounds-checked config.

Migrating from another TOTP library

Secrets from any RFC 6238 library keep their codes, so nobody re-enrols — re-encrypt the stored columns once. Fortify migration included.

Testing

TwoFactor::fake() swaps in a programmable, no-crypto double that records every call — or drive real codes against a frozen clock.

Requirements

PHP 8.4+, Laravel 12 or 13 and an APP_KEY — with our crypto, enums and package-toolkit packages as dependencies.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.