Skip to content

Sign-in and network safety

Valet holds keys to your services. By default there’s no sign-in, and so it’s read-only: a page anyone can reach must not be able to change anything.

With auth.mode: none, and that’s the default, the page shows everything but can’t act. There are no buttons, and the assistant gets no tools that add or change things. Requests to run an action get a 403 that says why.

There is no sign-in on the data, so anyone who can reach the page can read it. Keep it on a private network, or use one of the modes below.

The Compose file publishes on 127.0.0.1 for this reason.

To use the controls, either turn on sign-in, or say the network itself is the lock:

auth: { mode: none, trustNetwork: true }

Use trustNetwork only when nothing but your VPN, your tailnet or a sign-in proxy can reach Valet. Anyone who can reach the page can use its controls.

ModeWho it’s for
noneA private page. Read-only unless trustNetwork is true.
localBuilt-in accounts, kept in data/users.json.
proxyYou already have Authelia, Authentik or Tailscale Serve in front.
oidcSign in with an OpenID Connect provider.

All of these are read once. Restart Valet after changing auth.

auth: { mode: local }

On a fresh install, Valet prints a one-time setup code to its log. The first account has to present it, so whoever reaches the page first can’t claim it.

Terminal window
docker logs valet # find the setup code
docker exec -it valet node dist/server.js user add sam # or skip the code, and do it here

The same command takes passwd, remove and signout. signout ends that person’s sessions everywhere.

Passwords are hashed with scrypt. After five failed sign-ins from one address, each further try waits longer, up to 15 minutes.

“Keep me signed in” lasts auth.sessionDays, 30 by default. Without it, a session lasts a day.

auth:
mode: proxy
proxy:
header: Remote-User # the header your proxy puts the user in
trusted: [10.0.0.5] # the proxy's address, as Valet sees it

Valet believes the header only from the addresses in trusted, matched exactly. There are no ranges, so give the proxy a fixed address. In Docker, that’s its address on the network it shares with Valet.

The Authelia recipe has a working Caddy block.

auth:
mode: oidc
oidc:
issuer: https://auth.example.com
clientId: valet
clientSecret: ${OIDC_SECRET}
name: Example SSO # on the sign-in button
allow: [sam, alex] # empty: anyone the provider lets through

Register https://<your-valet>/api/auth/oidc/callback as the redirect URL at your provider. The issuer must serve /.well-known/openid-configuration.

claim picks which claim is the person’s name, preferred_username by default. allow takes names or emails. Set redirectUrl if Valet can’t work out its own address behind an unusual proxy.

Valet answers only to the names it’s meant to have. localhost and IP addresses always work. Any other name needs listing, or it gets a 421 with a message that says what to add.

allowedHosts: [valet.example.com, "*.home.example.com"]

This stops DNS rebinding: a web page elsewhere pointing its own domain at your Valet, and scripting your browser against it. Hosts has the detail. allowedHosts reloads live.

Any reverse proxy works. Keep the port on 127.0.0.1 if the proxy is on the same machine, add the public name to allowedHosts, and put X-Forwarded-For on the request. It’s only believed from the addresses in auth.proxy.trusted, where it’s used to throttle sign-in by the real client.

Valet streams live updates over server-sent events, so don’t buffer /api/events.

Caddyfile
valet.example.com {
reverse_proxy valet:7575
}

The threat model says what all this does and doesn’t protect against.