Configuration
Set these on the Mesh process and restart it:
Every field is required in external mode.
PUBLIC_URL must be HTTPS even for a
development instance using this flow. Other endpoint URLs require HTTPS except
that configuration permits HTTP in MESH_ENV=development or test, or for hosts
ending in .internal. That hostname exception is a deployment assumption, not
proof that the network is private.
Register the exact callback PUBLIC_URL/auth/callback with the external service.
The authorization endpoint must be reachable by the browser; exchange and JWKS
endpoints must be reachable by the Mesh server. Keep the exchange secret out of
browser configuration and source control.
Launch protocol
- The browser opens
GET /api/auth/external/start. Mesh creates a five-minute launch cookie containing a random state and verifier. - Mesh redirects to the authorization URL with
instance_id,state,challenge(base64url SHA-256 of the verifier), andredirect_uri. - The external service authenticates the operator and redirects to
/auth/callback?code=...&state=.... - Mesh checks the returned state, then POSTs JSON containing
code,code_verifier, andinstance_idto the exchange URL, authenticated withAuthorization: Bearer MESH_AUTH_EXCHANGE_SECRET. - The exchange service returns HTTP 200 with
{"assertion":"SIGNED_JWT"}. Mesh verifies it, consumes its unique ID against replay, and opens a local session before redirecting to/agents.
Assertion requirements
Mesh accepts an ES256 JWT with headertyp: mesh-launch+jwt and a kid selecting
an EC P-256 key from the configured JWKS. The payload must contain:
- Matching
iss, stringaud, andinstance_id. - Nonempty
suband single-usejti. token_use: mesh_launchandschema_version: 1.email_verified: trueand an email for first-time profile creation.- A
capabilitiesarray containingmesh:admin. iat,nbf, andexpcompatible with the verifier’s time checks: maximum declared lifetime 90 seconds and 30 seconds of allowed clock skew.
Accounts and sessions
An external user is identified by(issuer, subject), never by email.
Different external identities can share an email, and an external identity can
share one with a local account. Sign-in does not claim the older account, change
its password, transfer its API tokens, or rewrite its attribution. The operator
roster can therefore show distinct users with the same email.
External sessions last 15 minutes and use the secure, HttpOnly, host-only
__Host-mesh_session cookie. Session records are held by Mesh. Logout or local
session revocation ends that Mesh session; this flow does not implement logout
from the external identity provider. Disabling a Mesh operator also blocks their
session access.
Switching back to local mode does not give external users passwords. Local
password and invitation lookups consider local accounts only. Preserve existing
identity mappings and use a compatible database/binary when changing auth modes;
do not repair a login issue by merging accounts on email.
Implementation: internal/admin/auth_external.go (repository),
internal/admin/jwt.go (repository),
and internal/config/config.go (repository).