# Easy2FA keeps secrets in the URL fragment and tests itself by parsing its own index.html

> Easy2FA is a static, serverless TOTP generator where each account is a bookmarkable link with the secret in the fragment. Its security claims are enforced by a content security policy rather than by promises, its tests run against code extracted out of the single HTML file, and its clock check needs the one network request the page otherwise avoids.

**zeropl/2FA** — Computes a live 6-digit 2FA code from a secret, right in your browser. No server, no sign-up, no database: each "account" is a bookmarkable URL with the secret hidden in its # fragment, which browsers never send over the network.

- Repository: https://github.com/zeropl/2FA
- Stars: 480 · Forks: 65
- Language: HTML
- License: MIT
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/zeropl-2fa

## One bookmark is one account

The unit of storage is a URL. Each account link carries its secret in the fragment, in the form `#secret=…&label=acme-test`, and opening the bookmark produces a code that is already ticking. The repository description states the reason for that design choice in the fragment itself: browsers never send a fragment to the server, so the HTTP protocol has no mechanism to leak it in a request. The motivation section is narrower than general 2FA, which matters for judging it. The stated problem is accumulation: CI bots, staging back-accounts, crawler accounts, the demo login the whole team shares, all of which now demand a second factor. Mixing those into a phone authenticator buries them among banking codes and makes every phone migration painful, and handing over the phone is the only handover available. The trade offered is a bookmark folder as the dashboard and a link as the account.

## The deploy buttons clone the repository into your account

There is no build step and no bundler; the project is a folder of static files with one `index.html`, a service worker, a web manifest, a vendor directory holding the front-end libraries, and three deployment configuration files for Cloudflare, Netlify and Vercel. The one-click deploy row is three buttons, and each one clones this repository into your own GitHub account and deploys that copy, which is why the description calls the result free and auto-updating: you get your own fork rather than a shared instance. That is a meaningfully different trust model from a hosted tool, and it also means the deploy path assumes you have a GitHub account and that you will keep the fork. A GitHub Pages route is offered as the alternative, with a no-Jekyll marker file at the root for it. Nothing has to be compiled, so a fork that is three months stale still runs.

## connect-src self blocks fetches, and no CDN does the rest

The security section argues that a privacy claim you have to trust is weak, and then makes the browser verify it two ways. First, the page ships a content security policy with `connect-src 'self'`, which means fetch, XHR and websocket traffic to any other origin is refused by the browser regardless of what the page's own code asks for. The stated test is to open the developer tools network panel and see that every request points at your own domain. Second, the front-end libraries are vendored into the repository rather than loaded from a content delivery network, so there is no third-party script running with the secret in memory. Those two do different jobs and it is worth keeping them apart: a connect-src policy governs requests your page makes, not scripts your page loads, so a remote script tag would still execute unless script sources were restricted too. Vendoring is what closes that second door, and the policy is what makes a poisoned dependency unable to phone home.

## The clock check needs the one request the page avoids

Time-based codes fail quietly when the device clock is wrong, and a wrong code gets retried until the account locks you out. So on load the page compares the local clock against the `Date` response header from the server and warns loudly once the two are ten seconds or more apart. The reasoning is right and the threshold is worth understanding. The default period is thirty seconds, so ten seconds is a third of a step: below that threshold a page can still compute the code for the wrong step if the moment you load falls near a boundary, which is precisely the case the warning cannot see. The design tension is that this check requires a request, on a page whose central claim is that it makes none. It is a narrow one, a header read with no secret attached, and it is optional in practice since a page on a file or offline simply has nothing to compare against. What the page will not do is stay silent about a skew it can see.

## Ninety-eight tests pin the standard, not the extensions

The test story is the part that will age best. A test page ships with the project and runs ninety-eight self-tests that execute the production code itself, extracted straight out of `index.html` at run time, so the tests and the implementation cannot drift apart, and continuous integration runs them headlessly on every push. The vectors come from the official RFC 6238 test suite, which pins the algorithm to the specification's default configuration. The tool itself goes beyond that default: it computes with SHA-1, SHA-256 and SHA-512, and both the digit count and the period are configurable. Those extensions are outside what the published vectors cover, so ninety-eight passing tests establish that the standard path is right and say nothing about the non-standard configurations. The refusal rules are the other half of the same instinct. HOTP links are rejected outright rather than quietly computed as if they were time-based, and unknown algorithms or digit counts found in migration QR codes are dropped rather than guessed at.

## One file cannot be split without breaking the tests

Extracting the implementation from the HTML file is what makes the tests self-maintaining, and it also constrains the project. The test harness has to find the implementation inside `index.html`, so the structure of that file is part of the test contract: splitting the page into modules, moving the crypto into a separate script, or templating it would all break a suite that currently needs no build and no bundler to run. Given that the whole project is one static folder with no build step, that is a reasonable trade for now, and it is the kind of decision that should be revisited at the moment someone wants to add a second page. The rest of the root tells you what else is hand-maintained: a patches document, a separate support script, an icon in SVG plus an Apple touch icon, an assets directory and a file that excludes assets from something.

## Lending an account ships the secret, and the documentation admits it

Two features hand the whole account to someone else, and both are documented with the risk in italics rather than buried. Lending an account means sending the link; the teammate opens it and reads the live code without installing anything and without touching your password manager. The parenthetical says the link carries the secret, to share it over a trusted channel and rotate afterwards. Presentation mode is the same idea for a screen: appending a parameter to a link, or using the presentation button, shows a large rotating code with the QR and the secret-revealing controls hidden, so you can project or screen-share without the QR appearing on the wall. That one is labelled as on-screen protection only, since the link still contains the secret and hides nothing from whoever receives it. The honesty is the notable part, since a feature whose entire purpose is showing someone a code is exactly the place where a tool is tempted to overclaim.

## Conclusion

This is a small tool with an unusually clear threat model, and it is worth reading even if you keep your codes in a phone app, because the reasoning about what a static page can and cannot promise is transferable. Take it if you have a drawer of test accounts rather than personal banking codes, which is exactly the case it is built for. Three things to weigh. The one-click deploy buttons clone the repository into your own GitHub account, so self-hosted means a fork you own, not a binary you install. The clock check depends on a request the page otherwise avoids, so the tool needs connectivity once at load to tell you your clock is right. And lending an account means sending a URL that contains the secret, which the documentation admits in italics and advises you rotate afterwards.

## FAQ

### What is Easy2FA?

A stateless, self-hosted TOTP code generator that runs entirely in the browser as static files, with no backend, no database, no sign-up and no tracking. Each account is a bookmarkable URL whose secret sits in the hash fragment, which browsers never send to a server, and codes are computed locally with Web Crypto.

### How do I add accounts to Easy2FA?

Save each account as a bookmark with the secret in the fragment, in the form `…/#secret=…&label=acme-test`. For many accounts at once, drop a `.txt`, `.md` or `.yaml` list of links onto the page, paste secrets in, or scan a Google Authenticator migration QR to fill the multi-account board, which is only a local cache you can clear in one click.

### How does Easy2FA check whether my clock is right?

On load it compares the device clock with the server's `Date` response header and warns loudly when the two differ by ten seconds or more. The stated reason is that clock skew is the quietest way time-based codes fail, and most tools say nothing about it.

### Does Easy2FA support HOTP or unknown QR codes?

No, on purpose. HOTP links are rejected outright rather than silently computed as time-based codes, and unknown algorithms or digit counts in migration QR codes are dropped rather than guessed at, on the principle that displaying a confidently wrong code is worse than refusing.

## Sources

- [Issues](https://github.com/zeropl/2FA/issues)
- [License: MIT](https://github.com/zeropl/2FA/blob/main/LICENSE)
- [README](https://github.com/zeropl/2FA/blob/main/README.md)
- [zeropl/2FA on GitHub](https://github.com/zeropl/2FA)

---

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