# Auth model How sherlock obtains and distributes credentials. ## Mental model: wallet of OAuth sessions Sherlock is a wallet. Each entry is one OAuth session against one upstream service's Authentik provider (gitea, grafana, miniflux, invidious, …). There is **no master Authentik session**; every entry is service-keyed. This matches the homelab topology: caddy reverse-proxies the public endpoints and enforces edge OIDC for browsers; each downstream service *also* has its own Authentik OAuth client used to bind a service-side account to the operator's Authentik identity. An MCP that wants to call `gitea.x` needs a token issued by gitea's specific Authentik provider — caddy + gitea both verify it. The same goes for every other service. One master token cannot satisfy all of them. ## Authentication is lazy and per-MCP `sherlock login` does not exist. The operator does not preemptively authenticate. Each MCP at startup calls ```go ts, err := authn.Ensure(ctx, store, "gitea", cfg, opts) ``` `Ensure` (in `internal/authn`) handles every case: 1. Wallet has fresh tokens for `gitea` → return them. 2. Wallet has stale tokens with a usable refresh token → refresh under a cross-process flock, persist, return. 3. Wallet is empty (or refresh failed) → bind the loopback listener on `127.0.0.1:6990`, run a fresh OAuth PKCE flow against the service's Authentik provider (opens a browser via `xdg-open`), persist, return. The first time an operator runs `sherlock copilot` in a fresh environment, the agent spawns its MCPs; each MCP triggers its own browser flow as it needs one. Subsequent runs are silent until the refresh window opens. ## What lives in `services.d/.toml` (Phase 2) Each MCP needs an OIDC `Config` (issuer + client ID + scopes) to pass to `Ensure`. The operator-editable source of truth is ```toml [service] name = "gitea" issuer = "https://id.alexandru.macocian.me/application/o/gitea/" client_id = "Ig8...abc" scopes = ["openid", "profile", "email"] audience = "gitea.alexandru.macocian.me" # informational; aud check is on the IdP side [mcp] env_var = "GITEA_TOKEN" # what sherlock injects into the agent env (Phase 2+) ``` Sherlock-the-CLI does not read these in Phase 1 (the CLI has no authentication subcommands). Phase 2 MCPs load their own service's TOML at startup. See [service-registry.md](service-registry.md) for the full schema. ## Audience binding Per the MCP 2025-06-18 spec, downstream tokens MUST be audience-bound. Each Authentik provider issues tokens with `aud` set to its service's canonical name. Caddy and the upstream both verify it. Sherlock never tries to reuse one service's token for another. ## Scope minimisation Default service registrations request read-only scopes. Write scopes require an explicit override in the service's TOML (Phase 2). ## Out of scope for sherlock - Issuing SSH certs (gssh handles this — [gssh-integration.md](gssh-integration.md)). - Multi-user / shared tenancy. Sherlock is per-operator, full stop. - Acting as a Resource Server itself. - RFC 8693 token exchange. Tried during planning; rejected because each service's Authentik provider has its own consent screen and scope set and the exchange story across them is more brittle than just running N separate PKCE flows lazily.