# scheb/2fa adds second factors to a Symfony login without forcing one method

> This Symfony bundle implements two-factor authentication for the framework's security system, and it is split into sub-packages so that the core, the trusted device cookie, backup codes and each authentication method can be installed independently. The current 8.x line targets Symfony 7.4 and 8.0, with a 7.x line kept alive for bug and security fixes only.

**scheb/2fa** — Two-factor authentication for Symfony applications 🔐

- Repository: https://github.com/scheb/2fa
- Stars: 596 · Forks: 79
- Language: PHP
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/scheb-2fa

## The core bundle handles the security policy, not the codes

scheb/2fa is a bundle that provides two-factor authentication for a Symfony application. The distinction that matters is between the policy and the method.

The core package, scheb/2fa-bundle, supplies the machinery around the second factor rather than the second factor itself. Its listed features are an interface for custom two-factor authentication methods, trusted IPs, multi-factor authentication with more than two steps, CSRF protection, brute-force protection, authentication code reuse protection, whitelisted routes that stay accessible during the two-factor step, and fully customizable conditions for when to perform two-factor authentication.

Read that list as the answer to the question a team actually asks, which is not how do I compute a TOTP but what happens around it. Brute-force protection is the attempt limit. Reuse protection is the rule that a code cannot be submitted twice. CSRF protection covers the form that carries the code. The whitelist is what lets a user open a logout or a recovery link while the second factor is pending. The conditions are what decide whether a given login needs a second factor at all, and they are described as fully customizable, which means the trigger is a policy you write rather than a switch the bundle sets.

The interface for custom methods is the extension point. If you need a second factor that is not one of the packaged ones, you implement the interface and the rest of the flow, the limits and the protections stay as they are.

## Six packages, because not every app needs a backup code

The bundle is split into sub-packages so that you can choose the exact feature set you need and keep installed dependencies to a minimum. That is a deliberate packaging decision and it has a visible consequence: the number of Composer packages you add depends on which features you want.

On top of the core bundle there are two optional feature packages. scheb/2fa-trusted-device adds trusted devices, so that once a device has passed the second factor, it does not have to pass again. scheb/2fa-backup-code provides single-use backup codes for the case where you do not have access to the second factor device.

The methods themselves are separate packages too. scheb/2fa-totp implements time-based one-time passwords, scheb/2fa-google-authenticator supports Google Authenticator, and scheb/2fa-email sends an authentication code by email. The README also mentions QR codes to scan with a mobile device as a feature, which is the companion to the TOTP and Google Authenticator packages.

Two consequences follow from this split. A team that only wants TOTP installs the core bundle and the TOTP package, and avoids pulling in email sending and backup code machinery. And a team that wants a second factor that is not phone based, an email code or a hardware token, can install the core bundle with its custom method interface and add nothing else.

The package names are worth writing down exactly, because the core artifact is published on Packagist as scheb/2fa-bundle while the repository is scheb/2fa, and the feature and method packages follow the same naming pattern.

## Which branch you should be on is decided by your Symfony version

The version guidance table is the single most useful piece of information in the README for anyone starting, because the project maintains several lines at once and the status of each is different.

```bash
| [7.x][v7-repo]     | Bug & security fixes     | 6.4, ^7.0        |
| [6.x][v6-repo]     | EOL                      | 5.4, ^6.0        |
| [5.x][v5-repo]     | EOL                      | 4.4, ^5.0        |
```

So 8.x, the line with new features and bug fixes, targets Symfony 7.4 and 8.0, and it is the default branch of the repository. The 7.x line receives bug and security fixes only, and covers Symfony 6.4 and 7.0. Everything from 6.x backwards is marked end of life, which for a security component is the important fact: those lines no longer receive fixes, so a project still on 5.x or 6.x is running a two-factor implementation that nobody is patching.

The default branch of the repository is 8.x, which matches the maintenance policy rather than contradicting it. The badges in the README are also pinned to that branch, with the build status workflow and the code coverage graph both pointing at it.

One more piece of history is worth knowing. The repository contains bundle versions 5 and later, which are compatible with Symfony 4.4 or later, and the older unsupported versions live in a separate repository, scheb/two-factor-bundle, which also holds the 1.x through 4.x branches referenced in the table.

## A demo application you can run and a live one you can open

The repository includes a small test application for setting up locally and trying two-factor authentication in a real Symfony environment, and the README points to a readme in the app folder for the details. There is also a live demo at 2fa.scheb.de.

The app folder is a first-class part of the repository layout, alongside src, tests, bin, doc, composer.json and the configuration files, which include a .symfony.bundle.yaml, a .php-cs-fixer.dist.php, a phpcs.xml.dist, a psalm.xml for static analysis and a phpunit.xml.dist. There is a .codecov.yml for coverage and a .gitsplit.yaml, which is the configuration used to split the repository into the per-version branches that the version table points at.

The tooling tells you how the project is maintained. Static analysis with psalm, coding standards enforced with php-cs-fixer and phpcs, tests with phpunit, coverage measured through codecov, and a build status badge for the CI workflow. A security component with that much tooling around it is being maintained with more care than most, which is what you want from the code that guards your login form.

There is also an UPGRADE.md, which is the file to read when you move between branches, and a SECURITY.md for reporting a vulnerability, plus a CONTRIBUTING.md.

## A single maintainer, and a bundle that has been in use since 2014

The support section is short and personal. The maintainer says he has been developing the library since 2014, that he likes to hear from people using it because it is the motivation to keep working on his open source projects, and asks for a star on GitHub, with a donation link as a thank you option.

That is worth weighing in an honest way. A security component maintained by one person for more than a decade is a real strength in one sense, since the maintainer is deeply familiar with the code and has an obvious incentive to keep it healthy. It is a risk in another, since one person's availability is the project's availability. The mitigations visible in the repository are the branch policy, where several major versions receive fixes, the demo application, the test suite and the static analysis, and the fact that the MIT licence means a fork is legally unobstructed if that ever became necessary.

The release record shows the project is still active on the 8.x line. The published versions are v8.6.1 on 2026-07-10, v8.6.0 on 2026-06-12 and v7.14.0 on the same day, and that pairing is the clearest evidence of the policy in action, since a maintenance release went out on the 7.x branch at the same moment as a feature release on 8.x. The last push was on 2026-10-13, and the repository is not archived.

The licence is MIT, so there is no complication in shipping an application that uses it.

## Where the bundle does not reach

Three limits are visible in the documentation structure rather than hidden.

The first is scope of the framework. The bundle plugs into the Symfony security system, and it is described as providing two-factor authentication for your Symfony application, with the installation instructions and the full feature documentation both hosted on the Symfony bundles website rather than in the repository. The README itself is an index, so the detailed behaviour of the conditions, the trusted device handling and the brute-force settings lives outside the tree, and a team evaluating the bundle is reading external documentation to understand it.

The second is that the depth comes from the packages, not the core. If you want a second factor, you need at least two Composer packages, and each additional capability is another one. That is a reasonable trade for a component that has to stay small, but it does mean the number of things to configure grows with the feature set, and the trusted device and backup code packages in particular carry their own storage and lifecycle decisions.

The third is the multi-factor limit of the standard flow. The core supports more than two steps, which covers a password plus a code plus something else, but the packaged methods are authentication code based. A project that wants a passkey, a hardware token or a signed challenge response is not covered by the shipped packages and would use the custom method interface instead, with the corresponding amount of work on its own side.

Finally, the version guidance matters more than usual here. A project that upgraded Symfony and stayed on an end of life branch of the bundle would find that its second factor is no longer patched, and that is a decision the branch table forces you to make.

## Conclusion

scheb/2fa fits a Symfony team that wants a second factor in the standard login flow without hand-rolling TOTP validation, brute-force limits and code reuse checks, and it does not fit a project whose authentication is not the Symfony security system, or one that needs the backup code and trusted device features and therefore has to install the extra sub-packages deliberately. Before you start, pick the branch that matches your Symfony version, because 6.x and earlier are end of life and only 7.x and 8.x receive maintenance, and read the conditions configuration, since when the second factor triggers is fully customisable. The project is MIT licensed, release v8.6.1 was published on 2026-07-10, and the last push was on 2026-10-13.

## FAQ

### Which Symfony versions does scheb/2fa support?

The version guidance table marks 8.x as the line with new features and bug fixes, targeting Symfony 7.4 and 8.0, while 7.x receives bug and security fixes for Symfony 6.4 and 7.0. Versions 6.x and earlier are listed as end of life, and the repository holds bundle versions 5 and later, compatible with Symfony 4.4 or later.

### Which two-factor authentication methods does scheb/2fa ship with?

The method packages are scheb/2fa-totp for time-based one-time passwords, scheb/2fa-google-authenticator for Google Authenticator, and scheb/2fa-email for a code sent by email. The core bundle also exposes an interface so you can implement a custom method.

### What protections does the core scheb/2fa bundle add around the second factor?

The core features list includes trusted IPs, multi-factor authentication with more than two steps, CSRF protection, brute-force protection, authentication code reuse protection, whitelisted routes that stay accessible during the two-factor step, and fully customizable conditions for when two-factor authentication is performed.

### Can I skip the second factor for a trusted device with scheb/2fa?

Yes, through the optional sub-package scheb/2fa-trusted-device, which remembers a device once it has passed the second factor so that the second factor is not required again. Because the bundle is split into sub-packages, you install it only if you want the feature.

### How do I test scheb/2fa locally?

The repository contains a small test application that can be set up locally to try two-factor authentication in a real Symfony environment, with the details in the readme in the app folder. A live version of the same application is published at 2fa.scheb.de.

## Sources

- [Issues](https://github.com/scheb/2fa/issues)
- [License: MIT](https://github.com/scheb/2fa/blob/8.x/LICENSE)
- [README](https://github.com/scheb/2fa/blob/8.x/README.md)
- [Releases](https://github.com/scheb/2fa/releases)
- [scheb/2fa on GitHub](https://github.com/scheb/2fa)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/scheb-2fa
