Sign-in for private instances
You can limit who creates secrets and requests on your instance to people your sign-in proxy has signed in. Opening a secret, replying to a request and the manage links never need sign-in, so recipients and the people you ask for secrets don’t need an account. With GONE_AUTH unset, Gone is open to everyone, as it always has been.
Gone doesn’t sign anyone in itself. Your existing proxy does that (Cloudflare Access, oauth2-proxy, Pomerium, Authelia and others). Gone then checks the proof the proxy adds to each request.
How Gone trusts the proxy
Proxies hand over identity in one of two ways, and Gone treats them differently:
| Kind | What the proxy sends | What Gone checks | Safe when |
|---|---|---|---|
| Signed token | A JWT signed with the proxy’s or identity provider’s key | The signature against the provider’s published keys, the algorithm, issuer, audience, expiry, and a non-empty subject | Always, even if someone reaches Gone without going through the proxy |
| Plain header | A header such as Remote-User: alice | That the request came from an address in GONE_TRUSTED_PROXIES, and the header’s value | Only Gone’s network lets nothing but the proxy reach it, and the proxy removes the header when a visitor sends it |
Prefer a signed provider when your proxy offers one. A plain-header provider refuses to start without GONE_TRUSTED_PROXIES, and every provider prints a warning at startup when it’s missing.
List only your proxy’s own address in GONE_TRUSTED_PROXIES, never a whole network. With Docker’s published ports, a connection from the internet can arrive from the bridge’s gateway address, so trusting the bridge subnet would trust a header any visitor sends.
What Gone doesn’t do:
- It doesn’t store, log or show who signed in. It only decides “signed in or not”, and a refusal is counted in the
sign_in_denied_totalmetric with nothing about the visitor. - It doesn’t keep accounts, users or groups.
- It doesn’t filter by user or group. Allow or deny people in your proxy.
- It doesn’t redirect anyone. See What visitors see.
Providers
GONE_AUTH | Product | Kind | Header Gone reads | You set | Tested |
|---|---|---|---|---|---|
cloudflare | Cloudflare Access | signed (RS256) | Cf-Access-Jwt-Assertion | GONE_AUTH_TEAM, GONE_AUTH_AUDIENCE | Not yet |
google-iap | Google Cloud Identity-Aware Proxy | signed (ES256) | X-Goog-Iap-Jwt-Assertion | GONE_AUTH_AUDIENCE | Not yet |
pomerium | Pomerium | signed (ES256) | X-Pomerium-Jwt-Assertion | GONE_AUTH_HOST (optionally GONE_AUTH_JWKS_URL) | Not yet |
teleport | Teleport Application Access | signed (RS256) | Teleport-Jwt-Assertion | GONE_AUTH_HOST, GONE_AUTH_ISSUER, GONE_AUTH_AUDIENCE | Not yet |
oidc | Any OpenID Connect provider’s ID token, passed on by oauth2-proxy, Vouch or a similar proxy | signed (RS256 or ES256) | Authorization: Bearer, or GONE_AUTH_HEADER | GONE_AUTH_ISSUER, GONE_AUTH_AUDIENCE (optionally GONE_AUTH_ALG, GONE_AUTH_HEADER, GONE_AUTH_JWKS_URL) | Yes |
authelia | Authelia | plain | Remote-User | GONE_TRUSTED_PROXIES | Yes |
authentik | authentik proxy outpost | plain | X-Authentik-Username | GONE_TRUSTED_PROXIES | Not yet |
tailscale | Tailscale Serve | plain | Tailscale-User-Login | GONE_TRUSTED_PROXIES (usually 127.0.0.1) | Not yet |
custom | Anything else that sets a header: Okta Access Gateway, F5 BIG-IP APM, Microsoft Entra application proxy’s header-based sign-on, or nginx or Caddy renaming a header | plain | GONE_AUTH_HEADER | GONE_AUTH_HEADER, GONE_TRUSTED_PROXIES (optionally GONE_AUTH_FORMAT, GONE_AUTH_VALUE) | Yes |
Built but untested. Support for Cloudflare Access, Google IAP, Pomerium, Teleport, authentik and Tailscale is built from each vendor’s documentation of what it sends and covered by unit tests, but not yet tested against the live product. Google IAP’s key parsing is checked against Google’s real published keys. oidc (Dex behind oauth2-proxy) and authelia (behind Caddy) are tested end to end with the real software, and custom in Gone’s browser tests. If an untested provider refuses someone it should let in, Gone fails closed: creating answers 401 and sign_in_denied_total goes up, but nobody gets in who shouldn’t. Please open an issue if that happens, or to tell us one works.
On Kubernetes, only the signed providers are supported, because any pod in the cluster could send a plain header. Running Gone on Kubernetes explains why and what to use instead.
Every variable is described in Configuration. A setting that doesn’t apply to your provider stops the server, so a typo can’t go unnoticed.
Signed providers fetch keys. Gone downloads the provider’s public keys when it starts and every hour after, and again when it sees a key it doesn’t know (at most once every 5 minutes). It’s Gone’s only outbound connection. Until the keys load, nobody counts as signed in, and creating is refused.
Not built in yet: AWS Application Load Balancer sign-in, Azure App Service authentication, and a dedicated Microsoft Entra provider. Entra application proxy works today through custom. Ask if you need one of the others.
Paths to leave open
Recipients and the people you ask for secrets must reach these paths without signing in. Configure your proxy to let them through, and to require sign-in for everything else:
/secret/,/manage/and/reply/, with anything after them/request/followed by an ID (but not/requestitself, which is where requests are made)/api/secret/and/api/request/with a trailing slash and anything after it (but not/api/secretand/api/request, where secrets and requests are created)/api/policy,/static/,/i18n/,/healthzand/readyz
If your exemptions are broader than this, Gone still refuses to create anything without sign-in.
Some proxies protect a whole application and can’t exempt paths: Teleport, Tailscale Serve, and Google IAP unless you route the open paths to a second backend without IAP. With those, every recipient needs access through the same proxy too.
What visitors see
When sign-in is on and a visitor isn’t signed in:
- API:
POST /api/secretandPOST /api/requestreturn401with{"error":"sign in required"}. Nothing else changes. - The send and request pages show Sign in required instead of the form. If you set
GONE_AUTH_SIGNIN_URL, the page adds a Sign in with your identity provider button that links to it. Gone never redirects by itself. - The
goneCLI stops with “this server requires signing in through its web page”. It can’t create secrets or requests on a sign-in instance yet; opening, replying, status and revoke work as usual.
{return} in GONE_AUTH_SIGNIN_URL becomes the page’s own path (/ or /request), so the proxy can send the visitor back. Normally the proxy signs visitors in before Gone sees them, so this page only shows when the paths left open are broader than intended.
Recipes
Cloudflare Access
Built from Cloudflare’s documentation, not yet tested against a live Cloudflare Access; see built but untested.
- In Cloudflare Zero Trust, add a self-hosted application for your Gone hostname with a policy for the people allowed to create.
- Add a second application, or path rules, with the Bypass action for the paths to leave open.
- Copy the application’s Application Audience (AUD) Tag.
GONE_AUTH=cloudflare
GONE_AUTH_TEAM=acme # acme.cloudflareaccess.com
GONE_AUTH_AUDIENCE=4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2
GONE_TRUSTED_PROXIES=127.0.0.1 # cloudflared on the same hostoauth2-proxy with an OpenID Connect provider
oauth2-proxy passes the identity provider’s ID token on as Authorization: Bearer, and Gone verifies it against the provider’s keys. The same works for Keycloak, which recommends oauth2-proxy.
oauth2-proxy \
--provider=oidc --oidc-issuer-url=https://idp.example.com/realms/acme \
--client-id=gone --client-secret-file=/run/secrets/oidc \
--upstream=http://127.0.0.1:8080 --http-address=127.0.0.1:4180 \
--pass-authorization-header=true \
--cookie-refresh=4m \
--skip-auth-route='^/(secret|manage|reply)/' --skip-auth-route='^/request/.' \
--skip-auth-route='^/api/(secret|request)/' --skip-auth-route='^/api/policy$' \
--skip-auth-route='^/(static|i18n)/' --skip-auth-route='^/(healthz|readyz)$'GONE_AUTH=oidc
GONE_AUTH_ISSUER=https://idp.example.com/realms/acme
GONE_AUTH_AUDIENCE=gone
GONE_AUTH_SIGNIN_URL=/oauth2/start?rd={return}
GONE_TRUSTED_PROXIES=127.0.0.1Set --cookie-refresh shorter than your provider’s ID token lifetime, so the token Gone checks has not expired. Gone reads the keys from the issuer’s /.well-known/openid-configuration. Write GONE_AUTH_ISSUER exactly as that document’s issuer does, trailing slash included: tokens carry it verbatim.
Pomerium
Built from Pomerium’s documentation, not yet tested against a live Pomerium; see built but untested.
Turn on pass_identity_headers for the Gone route. Add a second route for the paths to leave open with allow_public_unauthenticated_access: true.
GONE_AUTH=pomerium
GONE_AUTH_HOST=gone.example.comAuthelia behind Caddy
Use Caddy 2.11.2 or later. Earlier 2.10 and 2.11 releases passed a visitor’s own Remote-User header through forward_auth (CVE-2026-30851).
gone.example.com {
forward_auth authelia:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
}
reverse_proxy gone:8080
}In Authelia’s access_control, give the open paths policy: bypass and the rest of the domain one_factor or two_factor:
access_control:
rules:
- domain: gone.example.com
resources: ['^/(secret|manage|reply)/', '^/request/.', '^/api/(secret|request)/', '^/api/policy$', '^/(static|i18n)/', '^/(healthz|readyz)$']
policy: bypass
- domain: gone.example.com
policy: one_factorGONE_AUTH=authelia
GONE_TRUSTED_PROXIES=172.20.0.10 # Caddy's address
GONE_AUTH_SIGNIN_URL=https://auth.example.com/?rd=https://gone.example.com{return}Any proxy that sets a header (custom)
With nginx in front of an auth service (auth_request), pass the signed-in user on under a name of your choice:
location / {
auth_request /oauth2/auth;
auth_request_set $email $upstream_http_x_auth_request_email;
proxy_set_header X-Gone-User $email;
proxy_pass http://127.0.0.1:8080;
}GONE_AUTH=custom
GONE_AUTH_HEADER=X-Gone-User
GONE_AUTH_FORMAT=email
GONE_TRUSTED_PROXIES=127.0.0.1GONE_AUTH_FORMAT:any(the default) accepts any value that isn’t empty;emailaccepts only a bare address.- Proof the request came through the proxy: to have the proxy send a fixed value as well, set
GONE_AUTH_FORMAT=exactand the same long random value inGONE_AUTH_VALUE. This proves the request passed through the proxy even if the network lets other traffic reach Gone. Generate one withopenssl rand -base64 24. - Header names: use letters, digits and dashes only. nginx drops headers containing underscores.