An offline, end-to-end encrypted authenticator for 2FA (TOTP) codes. It runs entirely in the browser as an installable PWA: no account, no server, nothing leaves the device.
A Bambotics project.
- Unlock with a passkey (Face ID, Touch ID, Windows Hello, security key) via the WebAuthn PRF extension, with an optional 6-digit backup passcode.
- Encrypted at rest. Accounts are stored in IndexedDB, encrypted with AES-256-GCM.
- Works offline after the first visit, and can be installed to the home screen or desktop.
- Add accounts by scanning a QR code with the camera, uploading or pasting a QR image, or typing the secret key. Supports SHA-1/256/512, 6–8 digits and custom periods.
- Encrypted backups. Export to a password-protected file and import it on another device.
- Move between apps. Import or export a plain-text file with one
otpauth://link per line (the format many authenticators export), or share a single account as a QR code or link. - Auto-lock, search, light/dark theme, English and Vietnamese.
A random 256-bit vault key encrypts all accounts. That key is never stored in the clear; it is wrapped once per unlock method:
| Unlock method | Wrapping key |
|---|---|
| Passkey | WebAuthn PRF output → HKDF-SHA256 → AES-256-GCM key |
| Passcode | PBKDF2-SHA256 (600,000 iterations) → AES-256-GCM key |
Adding or removing an unlock method only re-wraps the vault key. Backups are encrypted separately with a key derived from the backup password.
- The passkey is the strong factor. A 6-digit passcode protects against casual access, but someone who copies the browser profile can brute-force it offline. The lockout delay only slows guessing through the UI.
- Codes live only in this browser's storage. Clearing site data deletes them, so export a backup.
- The
otpauth://text export is not encrypted: anyone with the file can generate your codes. Delete it once it's imported where you need it. - Service icons are loaded from the aegis-icons CDN on jsDelivr, which reveals the issuer names (not secrets) of your accounts to the CDN.
- Passkey unlock needs a browser and authenticator that support PRF (recent Chrome, Edge, Safari 18+, Firefox 135+ with a platform authenticator or a FIDO2 security key). Otherwise, use the passcode.
cd client
npm install --legacy-peer-deps
npm run dev # http://localhost:5173
npm test # unit tests (Vitest)
npm run lint
npm run build # production build + service worker in dist/
npm run preview # serve dist/ at http://localhost:4173The service worker is only generated by npm run build, so test offline
behaviour with npm run preview. Passkeys need a secure context: localhost
or HTTPS.
React 18, Vite, TypeScript, HeroUI + Tailwind CSS, i18next, otpauth, qr-scanner, idb and vite-plugin-pwa (Workbox).
client/src/
├── lib/vault/ crypto, passkey (PRF), IndexedDB store, backup, lockout
├── lib/totp.ts code generation and otpauth:// parsing
├── providers/ VaultProvider: lock state, entries, auto-lock, tab sync
├── pages/ onboarding, unlock, home, settings
└── components/ OTP card, entry/QR modals, passcode input, FAB
client/dist is a static site. Host it on any static host over HTTPS (GitHub
Pages, Cloudflare Pages, Netlify, Vercel), with unknown paths falling back to
index.html.
The earlier full-stack version (Go server + PostgreSQL) is preserved on the
backup/fullstack-with-server
branch.