Password protection
How JustTunnel gates a tunnel with basic auth at the edge — what's enforced, what isn't, and where the password actually lives.
Password protection puts a basic-auth gate in front of a tunnel. Visitors hit the public URL and get a 401 with a WWW-Authenticate: Basic challenge before any request reaches your local server. The gate runs entirely on the JustTunnel edge — your application never sees the auth header, and the password never travels across the WebSocket.
It's the right tool for sharing a preview with a client, exposing a staging service to a handful of testers, or putting a soft barrier in front of a webhook receiver in development. It is not a substitute for application-level auth on production traffic.
How it works
When you set a password on a tunnel — either via --password on the CLI or per-tunnel in tunnels.yaml — the CLI sends the plaintext to the JustTunnel API once, the server hashes it with bcrypt, and stores the hash bound to your tunnel id. The plaintext is never written to disk on the server.
CLI sends plaintext (TLS)
│
▼
Server: bcrypt hash → store on tunnel record (db: password_hash)
│
▼
Inbound HTTPS request → edge checks Authorization header / session cookie
│
├── miss → 401 + WWW-Authenticate: Basic realm="JustTunnel"
└── hit → bcrypt.CompareHashAndPassword → forward over WebSocket
A few things to know about the gate itself:
- Compare uses bcrypt. Every request runs
bcrypt.CompareHashAndPasswordagainst the stored hash (internal/proxy/password.go:119). The cost factor is centralised ininternal/hashcost/hashcost.go. - Sessions cache successful auth. After a successful submission the edge sets a session cookie (
__jt_session) so subsequent requests skip the bcrypt compare. Sessions live for 24 hours. - Username is ignored. Visitors can type any username — the edge only checks the password.
- CSRF is enforced on the form. The auth form sets a
__jt_csrfcookie and validates it on submit; HMAC-signed with a server-side secret.
Where the password lives
| Layer | What it sees |
|---|---|
| Your CLI | The plaintext you typed (until the tunnel is registered) |
| Network in transit | TLS-encrypted to api.justtunnel.dev |
| JustTunnel server (DB) | Bcrypt hash only — password_hash column on the tunnel record |
| The JustTunnel edge runtime | Bcrypt hash for compare; never the plaintext |
| Your local server | Nothing. The auth header is consumed at the edge, not forwarded. |
That last row is the important one for debugging: if your local app is checking for Authorization headers and getting nothing, that's expected — basic auth at the edge doesn't pass through.
Brute-force protection
The edge rate-limits failed password attempts per source. From internal/proxy/password_ratelimit.go:
- 5 failed attempts per source within a 60-second sliding window triggers a block.
- Successful auth resets the counter.
- The window is per-tunnel; attempts against
tunnel-Adon't affecttunnel-B.
This is enough to defeat casual scanners. It is not a substitute for a strong password — pair the gate with a long random value when the audience extends beyond a small group of known recipients.
Limits and guarantees
What's enforced today:
- Password length: 4 to 128 characters. Validated CLI-side before the tunnel opens.
- Per-plan password-protected tunnel cap. Free: 1, Starter: 2, Pro and Team: unlimited (up to your
MaxTunnels). Frominternal/plan/limits.goand enforced atinternal/plan/enforcer.go:78. - Bcrypt at rest. No plaintext is stored on the server. There is no recovery — if you forget the password, set a new one.
- 24h session lifetime. A correct submission gates traffic for 24h via the session cookie; after that the visitor is challenged again.
- 5 attempts / 60s rate limit. Brief lockouts on repeated failures.
Best-effort, not guaranteed:
- Bot detection. The rate limiter handles dumb brute-force, not a distributed attacker. Use a long random password if abuse is plausible.
- Browser credential caching. Once a browser stores the basic-auth credentials, it sends them automatically. Closing all browser windows clears the cache; revisiting may or may not re-prompt depending on the browser.
Related
- Password-protect a tunnel — task-oriented walkthrough.
justtunnel [port]— the--passwordflag.- Use a tunnels.yaml config file — set passwords per tunnel in YAML.
- Tunnel anatomy — where the gate sits in the request path.
- Plans and limits — password-protected tunnel cap per tier.