# Authentication & pairing
## Pair a device with Grove
The [web dashboard](use-webapp.md) talks to a real HTTP daemon, so it needs to know who is calling.
Grove borrows the model your headphones already use: a new device asks to connect, both sides show
the same short code, and you approve it on the host. The daemon binds loopback, and pairing is how a
device earns access.
## The handshake
Pairing is a three-step handshake, out of band, and a browser's first visit lands on a pairing screen.
- **Step one, on the device.** Edit the suggested label, such as "Pixel 8", then request pairing. The daemon returns a code.
- **Step two, on the device.** The browser shows an eight-character code, `XXXX-XXXX`, and polls. Compare it against the host before approving.
- **Step three, on the host.** Confirm the matching code. Approval redirects the browser into the dashboard.
Step 1, on the device. Name it, then request pairing.
Step 2, on the device. The code to confirm on the host, expiring after five minutes.
Step 3, on the host. The TUI pops this modal on its own: approve with a, deny with d.
## Approving on the host
Both ways to approve run on the host, since approval never travels over HTTP: the TUI's own modal, or
the CLI on a headless host.
```bash
grove auth pending # list requests; each line shows the code
grove auth approve # approve the matching one
grove auth deny # reject it
```
Approve only when the codes match. That single check stops someone who merely reached the pairing
screen. The code expires after five minutes, and the device starts over.
## Managing sessions
A session lasts thirty days, renewing on every use, so a daily device pairs once.
```bash
grove auth sessions # active sessions with labels and expiry
grove auth revoke # lock a device out until it pairs again
```
The full reference lives on the [CLI page](use-cli.md#grove-auth).
## The security model
Grove's access control rests on keeping the daemon private and letting people in by hand.
- **Loopback by default.** `grove daemon serve` binds `127.0.0.1`, no blessed `--host 0.0.0.0`. Reach it from elsewhere by forwarding a port over SSH. See [reaching the dashboard from outside](use-webapp.md#reaching-it-from-outside-the-network).
- **Only two open endpoints.** `/healthz` and the pairing handshake answer without a session. Every other endpoint needs a session token as `Authorization: Bearer`.
- **Approval cannot cross the wire.** The daemon exposes deny over HTTP, never approve, so only the host's TUI or CLI can grant a request.
- **Secrets stay where they belong.** Session tokens live only as SHA-256 hashes, in `${user_config_dir}/grove/auth.json`, mode `0600`. The plaintext token reaches the device once, never touching disk. In the browser it never appears: the server holds it, giving an `HttpOnly` cookie.
## See also
- [Web dashboard](use-webapp.md): what the paired session unlocks.
- [CLI](use-cli.md#grove-auth): the `grove auth` command group.