One-time launch-code authentication
Agor can accept a generic external launch handoff when another trusted app has
already authenticated the user. The browser carries only an opaque, short-lived
launch_code; the daemon exchanges that code over a server-to-server
backchannel, verifies the returned assertion, maps a local user, and issues the
same runtime access and refresh tokens used by normal login.
Flow
- The external launch provider opens the runtime UI with
/ui/?launch_code=<opaque-code>. - The UI calls
POST /auth/launchonce with{ "launchCode": "..." }. - The daemon posts the code to the configured exchange endpoint with its runtime audience, instance ID, and optional service credential.
- The exchange endpoint returns a signed assertion for the authenticated subject.
- The daemon verifies issuer, audience, expiration, subject, and the configured
instance ID; then it maps or creates a local user by
(provider, issuer, subject). - The daemon returns normal runtime auth tokens. The UI stores those tokens and
removes
launch_codefrom the URL withreplaceState.
Configuration
external_launch:
enabled: true
exchange_url: https://launch.example.com/runtime/exchange
issuer: https://launch.example.com
audience: agor-runtime:my-instance
instance_id: my-instance
# Production: configure exactly one assertion verification method.
jwks_url: https://launch.example.com/.well-known/jwks.json
# Optional daemon-to-provider bearer credential. Prefer env vars for secrets.
service_credential_env: AGOR_EXTERNAL_LAUNCH_SERVICE_TOKEN
# Optional: allow role claims above member. Defaults to false.
allow_admin_roles: false
# Optional: primary action shown when launch sign-in cannot continue.
# Must be http:// or https://.
login_redirect_url: https://workspace.example.com/openFor local development only, a symmetric assertion secret can be used:
external_launch:
enabled: true
exchange_url: http://localhost:4000/exchange
issuer: http://localhost:4000
audience: agor-runtime:dev
dev_shared_secret_env: AGOR_EXTERNAL_LAUNCH_SHARED_SECRETFailure behavior and login redirects
When a launch code is missing, expired, already used, invalid, or cannot be exchanged because of a non-transient authentication failure, the UI removes the one-time code from the URL and shows a clear failure message.
If external_launch.login_redirect_url is configured, the unauthenticated
screen makes Return to workspace the primary action so the user can open a
fresh launch link from the external workspace. Local username/password login is
still available as a secondary fallback.
If login_redirect_url is omitted, the normal local login screen is unchanged.
Exchange contract
The daemon sends a JSON POST to exchange_url:
{
"launch_code": "opaque-one-time-code",
"audience": "agor-runtime:my-instance",
"instance_id": "my-instance"
}If service_credential or service_credential_env is configured, the daemon
also sends Authorization: Bearer <credential>.
The exchange endpoint should consume the launch code exactly once and return:
{
"assertion": "<signed JWT>"
}Required assertion claims:
iss: expected issuersub: stable subject at that issueraud: expected runtime audienceexp: short expiration time
Optional claims:
email,name,avatarorpicturerole:viewerormemberby default;admin/superadminonly whenallow_admin_rolesis explicitly enabledprovider: stable provider label used in local identity mappingjtiornonce: accepted for audit/correlation; one-time replay prevention remains the exchange endpoint’s responsibility
Required when external_launch.instance_id is configured:
instance_idorruntime_instance_id: must match configuredinstance_id
Security notes
- Put only an opaque, short-lived, one-time code in the browser URL.
- Do not put runtime bearer tokens or external provider tokens in URLs.
- The daemon-to-provider exchange should require HTTPS and an authenticated backchannel in production.
login_redirect_urlmust be an HTTP(S) URL; malformed URLs and schemes such asjavascript:are rejected during config loading.- Assertions should be audience-bound to the runtime, instance-bound when
instance_idis configured, and expire quickly. - Configure exactly one assertion verification method (
jwks_url,public_key, or dev-onlydev_shared_secret). - Local users are mapped by stable external identity
(provider, issuer, subject). A matching email alone never merges identities.