---
title: "Exceptions — Two-Factor for Laravel | Roundly"
description: "One TwoFactorException base class and eight typed failures — wrong code, enrolment state, rate limit, bad config, unusable secret and more."
url: https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/exceptions
language: en
---

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

[Two-Factor for Laravel](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel.md)

# Exceptions

Every exception extends the abstract TwoFactorException, so you can handle specific cases first and catch the whole family last:

```php
use RoundlyConsulting\TwoFactor\Exceptions\InvalidTwoFactorCodeException;
use RoundlyConsulting\TwoFactor\Exceptions\TwoFactorException;
use RoundlyConsulting\TwoFactor\Exceptions\TwoFactorNotPendingException;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;

try {
    TwoFactor::for($user)->confirm($request->string('code')->toString());
} catch (InvalidTwoFactorCodeException $e) {
    // wrong code — ask again
} catch (TwoFactorNotPendingException $e) {
    // nothing pending, or 2FA is already enabled
} catch (TwoFactorException $e) {
    // any other package failure
}
```

| Exception | Thrown when |
| --- | --- |
| `TwoFactorException` | Abstract base (a RuntimeException) — catch it for the whole family. |
| `InvalidTwoFactorCodeException` | confirm() receives a wrong code. |
| `TwoFactorNotPendingException` | Confirming with no pending enrolment — including when 2FA is already enabled. |
| `TwoFactorAlreadyEnabledException` | Starting enrolment for a user who already has 2FA enabled. |
| `TwoFactorRateLimitedException` | The built-in limiter locked the challenge; carries secondsUntilAvailable. |
| `InvalidTwoFactorConfigException` | An unknown algorithm, recovery\_codes.storage or replay\_guard; an out-of-range or non-integer digits, period, window, secret\_length, attempts.\*, recovery\_codes.count or cache.ttl value — including an explicit verify() window outside 0–2 and a cache.ttl below (2 × window + 1) × period; an attempts that is neither an array nor null; a non-string issuer or cache store; or a non-string table or column name. |
| `InvalidBase32Exception` | A secret is not valid base32 and cannot be decoded. |
| `InvalidTwoFactorSecretException` | A secret decodes but is no usable key — empty, under 10 bytes or all zero bytes — in currentCode(), verify() or attempt(); provisioningUri() throws it for an empty secret. |
| `TwoFactorAssertionFailedException` | A TwoFactorFake assertion failed. |

crypto-for-laravel’s own exceptions never escape: a base32 decoding failure becomes InvalidBase32Exception, and an OTP parameter failure becomes InvalidTwoFactorSecretException. Window, digits, period and secret length are bounded before they reach crypto, so the secret is the only parameter it can still refuse.

## 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/docs/two-factor-for-laravel.md)
