Get started
BETA
Browse docs
Concepts

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.CompareHashAndPassword against the stored hash (internal/proxy/password.go:119). The cost factor is centralised in internal/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_csrf cookie and validates it on submit; HMAC-signed with a server-side secret.

Where the password lives

LayerWhat it sees
Your CLIThe plaintext you typed (until the tunnel is registered)
Network in transitTLS-encrypted to api.justtunnel.dev
JustTunnel server (DB)Bcrypt hash only — password_hash column on the tunnel record
The JustTunnel edge runtimeBcrypt hash for compare; never the plaintext
Your local serverNothing. 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-A don't affect tunnel-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). From internal/plan/limits.go and enforced at internal/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.

On this page