Skip to content

Repository files navigation

OTP Share

OTP Share logo

Browser TOTP generation · Two sharing modes · Text/JSON API · Self-hosted

Generate TOTP codes locally, create setup links, share temporary code access, or expose the current code through a stateless API.

Report Bug · Request Feature

Highlights

  • Generates TOTP codes in the browser with no account or database required.
  • Supports SHA1, SHA256, and SHA512; 6- or 8-digit codes; and 15-, 30-, or 60-second periods.
  • Creates otpauth:// QR codes for authenticator apps.
  • Offers client-only setup links and Redis-backed temporary code links.
  • Returns the current code from /api/code as JSON or plain text.
  • Includes English and Simplified Chinese interfaces, plus light and dark themes.
  • Ships with a multi-stage Docker image that uses Bun for dependency installation and Node.js 22 for production, plus an ephemeral Redis Compose service.

Access Modes

OTP Share deliberately provides three modes with different trust boundaries:

Mode URL What the server receives Lifetime and access
Setup link https://host/#<secret> The HTTP request does not contain the fragment The recipient receives the complete TOTP secret and can generate future codes until the secret is rotated
Temporary link https://host/s/<id> Pre-generated OTP codes, never the TOTP secret UI options are 1, 12, or 24 hours, with optional burn-after-reading
Direct code API /api/code?secret=<secret> The TOTP secret on every request Stateless; returns the code for the server's current time and does not intentionally persist the secret

These modes are not interchangeable. A setup link protects the secret from the application server, but it gives the secret to the recipient. A temporary link limits what the recipient receives, while the direct API explicitly sends the secret to the server for calculation.

Quick Start

Requirements: Docker with the Compose plugin.

git clone https://github.com/Kadxy/OTP-share.git
cd OTP-share
docker compose up -d --build

Open http://localhost:3000. The Compose deployment needs no environment file, database migration, or initialization command.

To use another host port:

PORT=8080 docker compose up -d

Useful commands:

docker compose ps
docker compose logs -f app
docker compose up -d --build
docker compose down

The app container exposes only port 3000. Redis is reachable only from the internal Compose network.

Setup Links

For the default SHA1, 6-digit, 30-second configuration, a setup link contains only the normalized Base32 secret:

https://example.com/#JBSWY3DPEHPK3PXP

Non-default settings use this fragment format:

#<secret>.<algorithm-code>.<digits>.<period>

For example:

https://example.com/#JBSWY3DPEHPK3PXP.256.8.60

Algorithm codes are 1 for SHA1, 256 for SHA256, and 512 for SHA512. The three settings fields must be supplied together.

Setup-link fragments accept case-insensitive, unpadded Base32 secrets and ignore whitespace. This is intentionally stricter than the direct API, which also accepts valid Base32 padding.

URL fragments are not sent in HTTP requests or HTTP Referer headers. They are still visible to the recipient, browser history, the clipboard, browser extensions, screenshots, and any client-side code running on the page. Treat a setup link as the TOTP credential itself: it is not temporary and cannot be revoked without rotating the secret.

Legacy ?secret=... links are accepted for compatibility and rewritten to a fragment in the browser. Do not create new query-string setup links because the initial URL can be recorded by servers, proxies, and analytics systems.

Temporary Links

Temporary links are created entirely from the browser:

  1. The sender's browser calculates the codes required for the selected window.
  2. Only those generated codes and timing metadata are sent to /api/share.
  3. Redis stores the payload under a random seven-character ID with a TTL.
  4. The recipient opens /s/<id> and receives the code matching the current server time.

The UI offers 1-, 12-, and 24-hour windows. Burn-after-reading is enabled by default.

For burn-after-reading links, the first successful read atomically deletes the Redis payload and returns only a short display window of up to approximately three minutes to that first viewer. Later requests receive 410 Gone. The selected expiration primarily controls how long the unopened link remains available.

For reusable links, the first successful page load receives all codes remaining in the selected window. Once codes have reached a recipient's browser, deleting or restarting Redis cannot revoke that already-delivered data. The code window is aligned to TOTP periods, so its final usable code can end up to one period earlier than the Redis TTL.

Temporary links are bearer credentials: anyone who has the URL can open them. Generated codes are stored unencrypted in Redis memory; the underlying TOTP secret is not stored.

Direct Code API

GET /api/code returns the current TOTP code using the application server's clock. It does not require Redis.

Parameters

Parameter Required Default Accepted values
secret Yes Base32 secret; case-insensitive, whitespace ignored, valid padded or unpadded input, maximum raw length 1024 characters
token No Alias for secret; secret takes precedence when both are present
algorithm No SHA1 SHA1, SHA256, SHA512
digits No 6 6, 8
period No 30 15, 30, 60 seconds
format No json json, text

When format is omitted, the endpoint returns JSON unless the Accept header explicitly includes an allowed text/plain media range.

JSON

curl 'http://localhost:3000/api/code?secret=JBSWY3DPEHPK3PXP&format=json'
{
  "code": "123456",
  "algorithm": "SHA1",
  "digits": 6,
  "period": 30,
  "remainingSeconds": 18,
  "validFrom": "2026-07-12T10:29:30.000Z",
  "validUntil": "2026-07-12T10:30:00.000Z"
}

Plain text

curl 'http://localhost:3000/api/code?secret=JBSWY3DPEHPK3PXP&format=text'
123456

Plain-text success responses also include X-OTP-Remaining-Seconds and X-OTP-Valid-Until headers. Text bodies end with a newline for command-line use.

Errors

Invalid requests return HTTP 400 with one of these error codes:

  • MISSING_SECRET
  • INVALID_SECRET
  • INVALID_FORMAT
  • INVALID_ALGORITHM
  • INVALID_DIGITS
  • INVALID_PERIOD

Unexpected generation failures return HTTP 500 with INTERNAL_SERVER_ERROR. When format=text is valid, errors are returned as plain text; an invalid format always returns JSON.

The endpoint is intended for server-to-server, command-line, and same-origin use. It does not currently add cross-origin browser CORS headers, application-level authentication, or application-level rate limiting.

The API does not intentionally store the secret, but the secret is still sent through the application server in the URL. It may be recorded by browser history, shell history, reverse proxies, CDN/WAF products, APM tools, or access logs. Use HTTPS and redact the /api/code query string throughout the request path.

Local Development

Requirements:

  • Bun 1.3.8 or newer
  • Redis only if you need to create or open temporary links

Install dependencies and start the app:

bun install
bun run dev

The browser generator, setup links, QR generation, and /api/code work without Redis. To test temporary links, run a local Redis instance in another terminal or container. For example:

docker run --rm -d \
  --name otp-share-dev-redis \
  -p 127.0.0.1:6379:6379 \
  redis:7.4-alpine \
  redis-server --save "" --appendonly no

The default Redis URL is redis://127.0.0.1:6379. Copy .env.example to .env.local only when you need to override it.

cp .env.example .env.local

Development commands:

bun run dev
bun run lint
bun run build

Remove the example development Redis container with:

docker rm -f otp-share-dev-redis

Configuration

Variable Default Scope
REDIS_URL redis://127.0.0.1:6379 Application connection used only by temporary links; Compose sets it to redis://redis:6379
PORT 3000 Docker Compose host-port substitution, as in PORT=8080 docker compose up -d

When running the Docker image without the provided Compose file, set REDIS_URL to a reachable Redis instance if temporary links are required.

Storage Behavior

The provided Redis service is intentionally ephemeral:

  • RDB snapshots and AOF are disabled.
  • /data is mounted as a 16 MB tmpfs rather than a disk-backed volume.
  • Redis maxmemory is set to 128 MB. This is a Redis dataset limit, not a container or process memory limit.
  • The volatile-ttl eviction policy may remove active links before their configured TTL when Redis is under memory pressure.

Restarting only the app container preserves active links. Restarting, replacing, or removing the Redis container removes all links. This deployment is designed for disposable sharing, not durable storage or high availability.

Multiple app replicas can share the same Redis instance behind a load balancer. The included Redis service itself is not replicated or configured for failover.

Production and Reverse Proxy Checklist

  • Terminate HTTPS before exposing the application publicly.
  • Deploy the app at the origin root, such as https://otp.example.com/. The current client URLs and API calls do not support mounting the app below a subpath such as /otp-share.
  • Keep host, container, and user-device clocks synchronized with NTP. Browser codes use the device clock, /api/code uses the app-server clock, and temporary links rely on sender and server time alignment.
  • Bypass caching for /api/code, /api/share, and /api/share/*. The routes also send no-store response headers.
  • Redact /api/code query strings and avoid client analytics that collect setup-link fragments or full URLs.
  • Apply rate limits to /api/code and the share endpoints at the CDN or reverse proxy.
  • Cap /api/share request bodies at 512 KiB or less before they reach the app.
  • Do not publish the Redis port to the public network.
  • Remember that setup and temporary links are bearer credentials; there is no account-based access control.

Tech Stack

  • Next.js 16 App Router
  • React 19 and TypeScript
  • Bun 1.3.8 for package management and local development
  • Node.js 22 in the production container
  • Tailwind CSS 4
  • otplib for TOTP generation
  • next-intl for English and Simplified Chinese
  • Redis 7.4 for temporary links
  • Docker and Docker Compose

Contributing

Before opening a pull request, run:

bun run lint
bun run build

Bug reports and feature requests are welcome through GitHub Issues.

License

Distributed under the MIT License. See LICENSE for details.

About

Generate 2FA tokens in your browser and share them via secure, one-time links without ever revealing your secret key to the server.

Topics

Resources

Stars

Watchers

Forks

Used by

Contributors

Languages