GuideOne-Time Launch Auth

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

  1. The external launch provider opens the runtime UI with /ui/?launch_code=<opaque-code>.
  2. The UI calls POST /auth/launch once with { "launchCode": "..." }.
  3. The daemon posts the code to the configured exchange endpoint with its runtime audience, instance ID, and optional service credential.
  4. The exchange endpoint returns a signed assertion for the authenticated subject.
  5. The daemon verifies issuer, audience, expiration, subject, and the configured instance ID; then it maps or creates a local user by (provider, issuer, subject).
  6. The daemon returns normal runtime auth tokens. The UI stores those tokens and removes launch_code from the URL with replaceState.

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/open

For 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_SECRET

Failure 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 issuer
  • sub: stable subject at that issuer
  • aud: expected runtime audience
  • exp: short expiration time

Optional claims:

  • email, name, avatar or picture
  • role: viewer or member by default; admin/superadmin only when allow_admin_roles is explicitly enabled
  • provider: stable provider label used in local identity mapping
  • jti or nonce: accepted for audit/correlation; one-time replay prevention remains the exchange endpoint’s responsibility

Required when external_launch.instance_id is configured:

  • instance_id or runtime_instance_id: must match configured instance_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_url must be an HTTP(S) URL; malformed URLs and schemes such as javascript: are rejected during config loading.
  • Assertions should be audience-bound to the runtime, instance-bound when instance_id is configured, and expire quickly.
  • Configure exactly one assertion verification method (jwks_url, public_key, or dev-only dev_shared_secret).
  • Local users are mapped by stable external identity (provider, issuer, subject). A matching email alone never merges identities.
BSL 1.1 © 2026 Maxime Beauchemin