Skip to content

johnwmail/totp-authenticator

Repository files navigation

TOTP Authenticator

A lightweight, self-hosted TOTP authenticator that runs entirely in the browser. No server, no build step, no external crypto dependencies — just open and go.

Live demo: totp-1sr.pages.dev

Features

  • Per-account TOTP — configurable algorithm (SHA-1 / SHA-256 / SHA-512), period (30 s / 60 s / 5 min / 1 h / 1 d), and digits (6 / 8)
  • Issuer field for Google Authenticator compatibility
  • Auto-load accounts from accounts.json on first visit
  • Import / Export accounts as JSON or otpauth:// URIs
  • QR code generation for easy scanning into mobile authenticator apps
  • Click-to-copy codes and passwords to clipboard
  • Per-account password field — optional, hidden by default with 👁 toggle to reveal; click to copy
  • Responsive layout — works on desktop and mobile with consistent UI
  • Dark mode — toggle in dropdown menu, auto-detected by local time (08:00–22:00 = light), session override
  • Dropdown menu — Theme, Lock, Change Password, Import, Export, Share, Reload, and Reset consolidated into one menu
  • Modern UI — Inter font, JetBrains Mono for codes, gradient backgrounds, glassmorphism topbar, smooth animations
  • AES-GCM encryption — set a password to encrypt secrets at rest using PBKDF2-SHA256 (600 000 iterations); vault locks on reload
  • Drag-and-drop reorder in edit mode
  • Swipe-to-reveal actions on mobile cards
  • Countdown timer — shows remaining seconds; turns red and bold when ≤ 5 s
  • Clipboard auto-clear — copied values cleared after 30 s for security
  • PWA support — installable as standalone app via service worker and web manifest
  • Native Web Crypto API — HMAC-based TOTP generation with zero external crypto dependencies
  • CI/CD — GitHub Actions deploys to Cloudflare Pages on tag-based releases
  • Version injection — shows vDev locally; CI replaces it with the release tag

Cloudflare Wrangler (Local Dev & Deploy)

No config file needed — the project is a static site with no build step.

Local Development

# Install Wrangler (one-time)
npm install -D wrangler

# Start local dev server
npx wrangler pages dev . --port=8788

Then open http://localhost:8788 in your browser.

Deploy to Cloudflare Pages

# Login to Cloudflare (one-time, interactive browser flow)
npx wrangler login

# Deploy to production
npx wrangler pages deploy . --project-name=totp --branch=main

# Deploy to a preview branch
npx wrangler pages deploy . --project-name=totp --branch=preview

After deploying, your app will be available at https://totp.<your-account>.pages.dev.

Deploying via CI / Non-interactive (Environment Variables)

When running wrangler in CI or a headless environment, set these environment variables instead of wrangler login:

Variable Description
CLOUDFLARE_API_TOKEN Cloudflare API token with Pages: Edit permission
CLOUDFLARE_ACCOUNT_ID Your Cloudflare account ID (found in the dashboard URL)
export CLOUDFLARE_API_TOKEN="your_api_token"
export CLOUDFLARE_ACCOUNT_ID="your_account_id"
npx wrangler pages deploy . --project-name=totp --branch=main

Quick Start

Serve the files with any static HTTP server:

# Python
python3 -m http.server 8000

# Node
npx serve .

# BusyBox
busybox httpd -f -p 8000 -h .

Then open http://localhost:8000 in your browser.

Default Accounts

On first visit (empty localStorage), the app tries to fetch accounts.json from the server. If the file is missing or invalid, a random demo account is generated instead.

To seed your own accounts:

cp accounts.example.json accounts.json
# Edit accounts.json with your real secrets

The file supports // and # line comments.

Note: accounts.json is in .gitignore — it contains real secrets and should never be committed.

Account Format

[
  {
    "name": "user@example.com",     // Account name (required)
    "issuer": "GitHub",              // Service provider (optional, recommended)
    "secret": "BASE32SECRET",        // Base32-encoded TOTP secret (required)
    "algorithm": "SHA-1",            // SHA-1, SHA-256, or SHA-512 (default: SHA-1)
    "period": 30,                    // Seconds: 30, 60, 300, 3600, 86400 (default: 30)
    "digits": 6,                     // 6 or 8 (default: 6)
    "url": "https://github.com",     // Optional link on the account name
    "password": "s3cret"             // Optional associated password (hidden by default)
  }
]

Per-Account Password Field

Each account can store an optional associated password alongside its TOTP secret:

  • Add/Edit modal: new "Password (optional)" field, hidden by default (type="password"); click 👁 to toggle visibility
  • Account card: password row appears only for accounts with a password — masked as •••••••• by default; toggle to reveal/hide; click the password itself to copy (works whether masked or visible)
  • Import / Export: password is preserved under the password key in JSON
  • Optional — accounts without a password simply show no password row

Encryption

The app can encrypt all stored secrets at rest using AES-256-GCM.

  1. Enter edit mode → click ⋮ menu → Change Password to set a password.
  2. The password is run through PBKDF2-SHA256 (600 000 iterations) with a random 16-byte salt to derive a 256-bit AES key.
  3. Accounts are encrypted with AES-GCM (random 12-byte IV) and stored in localStorage under accounts_encrypted.
  4. Plain-text account data is removed.
  5. On reload the vault is locked — enter your password to decrypt and resume.

To lock the vault manually, click the 🔒 lock button in the topbar (visible when encrypted and unlocked).

To remove encryption, unlock the vault and clear the password.

All cryptographic operations use the browser's native Web Crypto API (crypto.subtle) — no external libraries.

Dark Mode

Click the theme icon in the dropdown menu (⋮ button in edit mode) to toggle between light and dark themes.

  • The theme is auto-detected by local time: 08:00–22:00 = light, otherwise dark.
  • Click the toggle to override for the current session.
  • Theming is implemented with CSS custom properties (data-theme attribute on <html>).
  • The toggle is available via the dropdown menu in edit mode (click the edit icon).

Import / Export

Action How
Export JSON Enter edit mode → click ⋮ menu → Export → downloads authenticator-export.json with all accounts.
Import JSON Enter edit mode → click ⋮ menu → Import → select a .json file matching the account format above.
otpauth:// URIs Import/export supports standard otpauth://totp/… URIs for interop with other authenticator apps.
QR Codes In edit mode, click the QR icon on any account to display a scannable otpauth:// QR code. Scan it with Google Authenticator, Authy, or any TOTP-compatible app.

Share URL

Share accounts via URL — no server required.

  1. Enter edit mode → click ⋮ menu → Share
  2. Enter a password and click Generate URL
  3. Click the URL to copy it
  4. Share the URL with anyone

Recipients open the URL → enter password → accounts merge into their local storage.

How it works:

accounts → lz-string.compress → AES-GCM.encrypt(password) → base64 → #data=...
  • Uses the same AES-GCM + PBKDF2 (600 000 iterations) as encryption at rest
  • URL data is stored entirely in the URL fragment (#data=) — never sent to a server
  • After 3 failed password attempts, the URL is cleared to prevent brute force
  • Imported accounts are merged with existing accounts (no duplicates by composite key: issuer + name + secret)

CI/CD Deployment

The project deploys to Cloudflare Pages via GitHub Actions (.github/workflows/ci.yml).

Trigger Branch / Ref Deployment
Push tag v* main Productiontotp-1sr.pages.dev + GitHub Release (zip/tar.gz)
Manual (workflow_dispatch) Preview deploy

CI Pipeline:

  1. Unit Testsnpm test
  2. E2E Tests — Playwright (Chromium, Firefox, Mobile Chrome, Mobile Safari)
  3. Deploy — only runs after both test jobs pass

The workflow uses cloudflare/wrangler-action@v3 and requires two repository secrets:

  • CLOUDFLARE_API_TOKEN
  • CLOUDFLARE_ACCOUNT_ID

Version injection

The source contains the placeholder vDev. During CI the workflow runs:

sed -i "s/vDev/${GITHUB_REF_NAME}/g" index.html

So a release tagged v1.2.0 will display v1.2.0 in the UI; local development always shows vDev.

Google Authenticator Compatibility

For maximum compatibility with Google Authenticator and other mobile apps:

  • Use SHA-1 algorithm (default)
  • Use 30-second period (default)
  • Use 6 digits (default)
  • Set the issuer field — Google Authenticator uses it to group and label entries

The generated otpauth:// URIs and QR codes follow the Key URI Format specification.

Storage

All data lives in the browser's localStorage:

Key Contents
accounts Plain-text JSON array of accounts (when encryption is off)
accounts_encrypted AES-GCM ciphertext + IV (when encryption is on)
accounts_meta PBKDF2 salt and iteration count

Use the Export button regularly to back up your accounts. localStorage can be cleared by the browser.

Tech Stack

  • Pure HTML / CSS / JavaScript — no framework, no build step
  • Web Crypto API (crypto.subtle) for HMAC-SHA-1/256/512 (TOTP) and AES-GCM + PBKDF2 (encryption)
  • qrcode.js vendored in lib/ — QR code generation
  • lz-string vendored in lib/ — compression for Share URL feature
  • Service Worker — offline-capable PWA with cache-first strategy (sw.js, manifest.json)
  • CSS custom properties for light / dark theming
  • Playwright for E2E testing

Testing

tests/
  unit/totp.test.js    # Unit tests (Node.js)
  e2e/e2e.spec.js      # E2E tests (Playwright)

Unit Tests

npm test

E2E Tests (Playwright)

# Install browsers (one-time)
npx playwright install --with-deps chromium firefox webkit

# Run all tests
npm run test:e2e

# Run with UI
npm run test:e2e:ui

# Run specific browser
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=mobile-chrome
npx playwright test --project=mobile-safari

Test coverage:

  • Account CRUD (add, edit, delete)
  • Import / Export JSON
  • TOTP code display and clipboard
  • Dark mode toggle
  • Encryption (set password, lock/unlock)
  • QR code modal
  • Share URL (generate URL with password, import from URL)
  • Mobile responsive layout

License

Licensed under the GNU General Public License v3.0.

About

A lightweight, self-hosted TOTP authenticator web app — no server required, runs entirely in the browser.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages