Contents

Browser Sign-In for a Self-Hosted Yoke Server

Browser admission on the server described in Self-Host Yoke. Company sign-in is optional; API-token clients work with either choice.

The server serves the universe web dashboard at its own URL — the same web dashboard Local and Hosted beta (stage.upyoke.com) serve. Run yoke ui up on a connected CLI to open it. A signed-in browser works with full read and write as its own actor: the web dashboard calls POST /v1/functions/call with the session cookie, and the engine's per-actor permission model decides what that actor may do, exactly as for an API token.

Without company sign-in. yoke ui up exchanges the CLI's API token for a single-use link valid for two minutes. The server stores a public selector and the SHA-256 hash of its secret, compares the hash in constant time, and consumes it atomically when minting a normal browser session for the token's actor. The secret travels in a URL fragment and same-origin POST body; it is removed from browser history before redemption and is never a request URL. The CLI passes the link directly to the browser and never prints it, including in JSON. Expired, used, or invalid links name browser_sign_in_expired, browser_sign_in_used, or browser_sign_in_invalid and tell you to run yoke ui up again. Actor disablement also blocks redemption. Agents, machines without a display and --no-browser receive a named browser reason and the recovery: run yoke ui up from your own terminal on a machine with a display. The signed-out page teaches this CLI path.

With company sign-in. yoke ui up opens the server's own URL, and the OIDC admission ladder below applies. Token-link minting and redemption are disabled, including links minted before OIDC was enabled. Any partial OIDC configuration fails by name rather than falling through to token sign-in. The CLI discovers this method without an API token; company sign-in remains reachable even when the machine's token needs replacing. Providers can be anything that speaks OpenID Connect with discovery: Okta, Keycloak, Microsoft Entra ID, Google Workspace, and others.

Signing out. Profile → Sign out posts to POST /v1/auth/sign-out, which revokes the browser's session and clears its analytics attribution cookie. Either sign-in path links that browser's analytics visitor id to the signed-in actor; clearing the cookie at sign-out starts the next person on a shared browser under a fresh visitor id (visitor links). Sign-out is same-origin only and refuses with cross_origin_refused otherwise.

What protects the cookie. The session cookie is HttpOnly and SameSite=Lax, so browsers do not attach it to cross-site writes. Every cookie-authorized call must also be same-origin: a present Origin must name the server's own host (X-Forwarded-Host from a declared trusted proxy, otherwise Host), and a present Sec-Fetch-Site must be same-origin or none. Anything else is refused as cross_origin_refused. A request carrying Authorization: Bearer is authenticated by its token, never by a cookie riding along. The browser identity also counts as "a person at a browser" for decisions only a person may take, such as clearing a merge-candidate review from the Inbox. Token-link redemption enforces the same Origin and Sec-Fetch-Site checks. Cookies are Secure on HTTPS; declare your TLS proxy using YOKE_API_TRUSTED_PROXIES as described below.

Proxy forwarding

Set YOKE_API_TRUSTED_PROXIES in the bundle's .env to comma-separated proxy IP addresses or CIDRs, for example 127.0.0.1,172.20.0.2. Use the transport peer addresses seen inside the core container: a host-side proxy may reach it through Docker's bridge gateway rather than loopback. An unset setting trusts only 127.0.0.1; an empty value trusts none. The server entrypoint also accepts --trusted-proxies. Malformed IPs/CIDRs, hostnames, empty list entries, and * refuse startup as trusted_proxies_invalid; correct .env and restart using the command below. Hosted and Local startup keep their existing configuration.

The proxy must preserve the external Host or set X-Forwarded-Host to it, set X-Forwarded-Proto: https, and overwrite or safely append X-Forwarded-For with the connecting client IP. Strip caller-supplied forwarding headers at the public edge. Restrict backend access to the proxy; avoid broad networks containing untrusted clients. The server accepts scheme/client/host forwarding only from declared transport peers; headers from other peers are ignored. This makes token cookies Secure, permits the HTTPS analytics collector, and gives each client its own rate budget.

Restart with yoke self-host init --dir PATH --protect-existing --start after editing .env. If collector_https_required appears, check the configured peer address and proxy headers, then restart with that command.

Local and self-hosted browser function calls share payload validation. They require a JSON object in payload; use {} for an empty payload. Other shapes receive HTTP 422 with envelope_invalid and a recovery message before the function is dispatched. This includes null, lists (even lists of key/value pairs), strings, numbers, and booleans. Omitting payload supplies an empty object.

The web dashboard, its /assets/ roster, and /served-build (the commit the server serves, read by browser QA) are served at the server's site root, so expose the server at a host root rather than under a path prefix.

1. Register a client at your provider. Create a confidential "web application" client with the authorization-code flow, scopes openid email profile, and this redirect URI (your server's external base URL plus the fixed callback path):

https://yoke.internal/v1/auth/oidc/callback

2. Wire the bundle. In the bundle directory, write the client secret as an owner-only file and enable the commented blocks:

printf '%s\n' '<client-secret>' > secrets/oidc-client-secret
chmod 600 secrets/oidc-client-secret

Then uncomment the OIDC lines in .env (YOKE_OIDC_ISSUER, YOKE_OIDC_CLIENT_ID, YOKE_OIDC_REDIRECT_URL, YOKE_OIDC_CLIENT_SECRET_FILE), then run yoke self-host init --dir PATH --protect-existing --start. Setting some vars but not all fails loudly: the door answers 409 naming what is missing.

The host operator opens the mode 0600 client secret and streams it through Docker exec stdin. After receiving the inputs privately, the bootstrap clears supplementary groups and drops to the image's yoke user, which writes private mode 0600 copies in tmpfs before starting the server. Host files are never mounted or chowned. The same handoff protects the database DSN and GitHub App key. Compose drops every ambient capability, grants only SETGID and SETUID, and enables no-new-privileges. Bootstrap and healthchecks prove the runtime has no effective Linux capabilities after the drop. Restart through the same host command so it can reopen the inputs; automatic restart is disabled.

3. Decide who gets in. Visiting https://yoke.internal/ (or any web dashboard page) signed out offers "Sign in"; after the provider round-trip the server admits the verified identity by the first matching rule and opens the web dashboard for that actor:

  1. Already linked — the identity (issuer + subject) was linked to an actor by a previous sign-in or an admin pre-link.
  2. Pending invite — a pending invite matches the verified email (case-insensitive); accepting it links the identity and grants the invite's org role, if one was set.
  3. Verified-domain membership — a verified email matching the enabled org domain creates a member actor without a role grant.
  4. Otherwise the sign-in is refused with an operator-facing reason.

Admission administration is org-admin surface on the yoke CLI:

yoke invite pat@corp.example --role admin
yoke invites list
yoke invites revoke email-7
yoke organizations domain set corp.example  # or: --clear
yoke settings set --set membership.auto_join_domain_verified=true
yoke identity link set --actor <actor-id> --issuer <iss> --subject <sub>
yoke identity link set --actor <actor-id> --email pat@corp.example

Email-only pre-links create invites on local and self-hosted universes. A hosted tenant refuses them with hosted_invites_platform_owned; use hosted invite commands or the org's Team page. Direct issuer/subject links remain available.

Each invite, revoke, link and domain write records the calling actor as its author (invited_by, revoked_by, created_by, changed_by). A call that carries no numeric actor is refused as actor_required; run it from a signed-in connection, a registered harness session, or a machine whose operating actor yoke setup recorded.

These invites are the server's own. On Hosted beta (stage.upyoke.com) Platform owns invites; the same yoke invite and yoke invites commands reach Platform's invites over a hosted connection, and the hosted universe's engine refuses them as hosted_invites_platform_owned. See Hosted org administration.

Email trust is strict by default: invites and domain membership match only when the provider marks the email verified. For providers that omit the email_verified claim entirely, opt in with YOKE_OIDC_ALLOW_UNVERIFIED_EMAIL=true (an explicit false from the provider is never trusted).

Connect your own machine

With company sign-in configured, run yoke connect https://<server> or choose A team server in yoke setup. Open the printed approval link, sign in, compare its Match code with your CLI, and select Approve my machine. Sign-in returns you to that web dashboard page. The CLI receives a machine-bound credential once and verifies it before saving the connection. Org membership is required; approving your own machine does not require an org-admin role.

Codes expire after ten minutes. A denied, consumed, or expired code requires a fresh connection. A temporary credential-store failure remains retryable while the code is live. Without OIDC, a first machine uses an explicit API token (the first-boot admin token remains the bootstrap); your own next machine runs yoke connect https://<server> and you approve its code from a machine you already connected with yoke machines approve CODE; compare the four-character match code and the displayed approver's identity first. yoke machines pending lists waiting machines and yoke machines deny CODE refuses a request. See Machine authorization.

Browser sign-in · Yoke