---
title: "Two-Factor for Laravel — docs | Roundly"
description: "Developer docs for Two-Factor for Laravel: TOTP enrolment, QR provisioning, replay-safe verification, recovery codes, rate limiting, events and testing."
url: https://roundly-consulting.com/open-source/docs/two-factor-for-laravel
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

Open source

# Two-Factor for Laravel

Install

`composer require roundly-consulting/two-factor-for-laravel`

Requires: PHP ^8.4 · Laravel ^12.0|^13.0

[GitHub](https://github.com/roundly-consulting/two-factor-for-laravel) [Packagist](https://packagist.org/packages/roundly-consulting/two-factor-for-laravel) [Donate (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Patreon](https://www.patreon.com/cw/roundly)

## 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](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/installation.md)

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

#### [Configuration](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/configuration.md)

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

#### [Model setup](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/model-setup.md)

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

#### [The TwoFactor facade](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/facade.md)

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

#### [DI and actions](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/without-facade.md)

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

#### [Enrolment](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/enrolment.md)

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](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/qr-codes.md)

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](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/verification.md)

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

#### [Recovery codes](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/recovery-codes.md)

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

#### [Brute-force limiter](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/rate-limiting.md)

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](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/replay-protection.md)

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](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/model-api.md)

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](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/events.md)

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

#### [Exceptions](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/exceptions.md)

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

#### [Security model](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/security.md)

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

#### [Migrating from another TOTP library](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/migrating.md)

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

#### [Testing](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/testing.md)

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

#### [Requirements](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/requirements.md)

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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source.md)
