2FAuth: a self-hosted web vault for TOTP and HOTP codes
A Web app to manage your Two-Factor Authentication (2FA) accounts and generate their security codes
At a glance
- What is it?
- 2FAuth stores your 2FA secrets in a database you control and serves the codes through a browser, on desktop or mobile. It is a Laravel and PHP 8.4 application under AGPL-3.0, and the interesting question is not what it does but whether you want your second factor living on a server you patch yourself.
- Who is it for?
- Adopt 2FAuth if you already run a server you patch, you want your 2FA secrets in a database you can dump and restore, and you are willing to keep a copy of APP_KEY somewhere that is not that server. Do not adopt it if you want the authenticator to live on the same phone as the accounts it protects, or if you cannot commit to the upgrade cycle.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 13 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 16, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The smartphone-loss problem 2FAuth is built around
The README states the motivation directly: the author wanted 2FA accounts stored in a standalone database that can be backed up and restored, after encountering a phone loss with accounts held in Google Authenticator. That is the specific failure 2FAuth addresses. A phone-bound authenticator makes the secret co-resident with the device, so losing the device means working through recovery codes for every account at once. 2FAuth moves the secrets to a server-side database, which turns the problem into a backup problem, and a backup problem is one you can solve with a dump and a copy.
The second motivation is ergonomic. The README says the author dislikes taking out a smartphone to get an OTP while working at a desktop, and that most generator UIs show all tokens at once with what it calls stressful countdowns. 2FAuth is therefore aimed at people who work primarily from a browser and want the code in a tab rather than in a handset. The audience is the self-hosting crowd: someone already running Docker or a small VPS, comfortable with a .env file, and willing to treat authentication infrastructure as something they operate. It is not aimed at someone who wants an app store install and no server.
Laravel, OTPHP and where the secrets actually sit
The stack is visible from the requirements and the repository metadata. It is a PHP application requiring PHP ^8.4, built on Laravel, and it needs any database Laravel supports. The README points to the Laravel server requirements page for the rest, which in practice means a web server and the usual PHP extensions rather than a fixed list published here. Code generation is delegated to the Spomky-Labs/OTPHP library, with the README stating compliance with RFC 4226 for HOTP and RFC 6238 for TOTP. Steam Guard codes are also supported, which is a separate algorithm path from the two RFCs.
The data flow is conventional for a server-rendered web app. Accounts are created by scanning a QR code, which the app decodes, or through an advanced form for manual entry when no QR code exists. Each account row holds the shared secret plus metadata, and accounts can be grouped. When you request a code, the server computes it from the stored secret and the current time or counter and returns it to the browser. That last detail matters more than it might appear: the secret never leaves the server, but neither does the computation, so the server is in the trust path for every code you generate. There is no client-side derivation described in the material.
Encryption is optional and off by default. The README says sensitive data stored in the database can be encrypted to protect against database compromise, and that this is provided as an option which is disabled by default. It also says the APP_KEY value in .env should be backed up when encryption is on. That is the honest framing of the trade-off: with encryption off, a database dump is a plaintext list of every 2FA secret you own. With it on, the dump is useless without the key, and the key becomes the thing you must not lose. Neither state is free.
Getting an instance up and the settings that matter
The README lists four installation paths: self-hosted server, Docker via CLI, Docker via Compose, and Heroku, each with its own page under docs.2fauth.app. The Docker routes are the ones most people will take. The exact image name, port mapping and volume layout are not in the material supplied here, so check the Docker CLI and Docker Compose pages before running anything; do not guess a compose file.
The configuration keys that appear in the material are few but load-bearing. APP_KEY in the .env file is the encryption key, and the README says it should be backed up, or the whole .env file should be, when encryption is on. Encryption itself is a setting that ships disabled. Registration behaviour is configurable: you can allow public registration, limit registration by email domain name, turn registration off entirely, or restrict it to SSO only. Multi-user support arrived in version 4.0. Auto logout after an inactivity period is on by default and can be deactivated, or configured to fire when a security code is copied.
Authentication supports security keys such as a Yubikey or a Titan key, and the traditional login form can be disabled in favour of them. That combination is worth thinking through before you enable it: if you disable the password form and lose the key, the recovery path is whatever the documentation describes, and it is not described in the material here. Imports are supported from 2FAuth JSON, Google Authenticator QR codes, Aegis Auth JSON and plain text, and 2FAS Auth JSON. The QR-code route means migrating one account at a time from Google Authenticator rather than in bulk.
Encryption off by default is the decision to think hardest about
A default is a statement about who the software expects to use it. Here the default is a plaintext secret store behind a login. For a single-user instance on a machine only you touch, that is a defensible position, and it keeps the recovery story simple: restore the database and you are back. For a multi-user instance, or any instance where the database file might end up in a backup bucket, a snapshot, or a hosting provider's storage, the default is the wrong one. The README acknowledges this by recommending the APP_KEY be backed up when encryption is on, which is an implicit admission that losing it is the failure mode to worry about.
The second failure mode is availability. A server-hosted authenticator is only as available as the server. If the host is down, or the container will not start after an upgrade, or the disk fills, you cannot produce a code, and you may be locked out of the accounts that depend on it. The README's own framing, that this is about a database you can back up and restore, is the mitigation, but only if you have actually tested the restore. A backup you have never restored is a hypothesis.
The third is that this is a web application handling second factors. It is exposed to the network by design, since you want to reach it from a desktop browser and a phone. That exposure is the cost of the convenience the project is selling. The material does not describe rate limiting, IP allowlisting, or any network-level control, so if you need those, they come from your reverse proxy, not from 2FAuth.
Against a phone authenticator and against a password manager
The obvious alternative is the thing it replaces: a phone-based authenticator such as Google Authenticator, Aegis, or 2FAS. The difference is not features, it is where the secret lives and what happens when a device dies. A phone authenticator keeps the secret on the device and typically offers export only through a QR-code migration flow, which is why the README's import list includes QR codes from Google Authenticator. 2FAuth keeps the secret in a database and gives you a JSON export. You trade device independence for server dependence, and you trade a device you already carry for a service you now have to run.
A second alternative is a password manager that also stores TOTP seeds, which many do. The difference in approach is the separation of factors. If the password and the second factor sit in the same vault, then one compromise yields both, and the second factor stops being a second factor. 2FAuth exists to keep them apart: the password stays in your password manager, the OTP secret stays in 2FAuth. That argument is the strongest reason to run something like this rather than adding TOTP to your existing vault, and it is the reason a self-hosted instance is preferable to a hosted OTP service, where the secrets are again co-located with a third party.
What 2FAuth does not do, based on the material, is act as a general secrets manager. It stores 2FA accounts and generates codes. There is no mention of password storage, file attachments, or sharing between users beyond the multi-user account model. If you want one vault for everything, this is the wrong tool, and deliberately so.
Upgrade discipline and what AGPL-3.0 means for a self-hoster
The release history in the repository metadata shows a steady cadence: v8.0.0 in early July 2026, v8.0.1 two days later, and v8.0.2 in early September 2026. That pattern, a major version followed quickly by a patch, is normal for a project of this size and it means you should read release notes before jumping a major version rather than pulling latest on a whim. The README points to a dedicated upgrade guide on docs.2fauth.app, which implies migrations are part of the upgrade path, as they are in any Laravel application. Budget for a database migration on major upgrades and take a dump first.
The licence is AGPL-3.0. For an individual running it on their own server for their own accounts, the practical effect is that you receive the source and can modify it. The copyleft obligation attaches when you distribute the software or offer it to others over a network as a modified version; running an unmodified instance for yourself does not trigger anything you need to act on. If you intend to host 2FAuth for other people as a service, or to fork it and expose the fork, read the licence text itself rather than a summary. This is not legal advice and the AGPL has been interpreted differently in different jurisdictions.
Maintenance cost is the real recurring expense. You are running PHP 8.4, a Laravel application, and a database. That means tracking PHP releases, applying 2FAuth upgrades, and keeping the database backed up. The project is not archived and is actively released, so the upgrade path exists; the work of walking it is yours. If you cannot commit to that, a phone authenticator with a printed set of recovery codes is a lower-maintenance answer to the same problem.
Editorial conclusion
Adopt 2FAuth if you already run a server you patch, you want your 2FA secrets in a database you can dump and restore, and you are willing to keep a copy of APP_KEY somewhere that is not that server. Do not adopt it if you want the authenticator to live on the same phone as the accounts it protects, or if you cannot commit to the upgrade cycle. Before trusting it, turn encryption on and confirm you can still generate a code after a restart, then export a JSON backup and import it into a second instance to prove the restore path works.
Community notes