Docs

Protocol

The relay gives a service a public HTTPS origin for OAuth redirects and WebAuthn ceremonies. It stores nothing about users and verifies nothing; the service does. There are no credentials: a service is identified by its slug (the first label of its sign-in address https://<slug>.login.mylogin.world.compas.cs.stonybrook.edu; clients treat it as opaque), and the only thing a transaction can do is send the browser back to the app URL configured for that service in the dashboard.

Base URL: https://api.mylogin.world.compas.cs.stonybrook.edu. The same API is served under /v1/ on every sign-in host. Errors: { error: { code, message, request_id } }.

Providers

GET /v1/providers → list of { id, name, kind: 'oidc'|'oauth2', managed, managed_client_id?, authorization_endpoint, token_endpoint, issuer?, jwks_uri?, userinfo_endpoint?, scopes, color, icon, byo_fields }. It is the live catalogue: color and icon (full-colour SVG) are for drawing buttons, and byo_fields lists what a customer's own client needs (client_id, client_secret, issuer, team_id, key_id, private_key). managed means the relay holds its own client for that provider.

Transactions

POST /v1/services/<slug>/transactions

{ "ttl_sec": 300, "message": "optional line shown on the page", "return_url": "https://192.168.1.20/private",
  "methods": [
    { "type": "oauth", "provider": "google", "mode": "byo", "state": "<random>", "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=<redirect_uri>&response_type=code&scope=openid%20email%20profile&state=<state>&nonce=<nonce>&code_challenge=...&code_challenge_method=S256" },
    { "type": "oauth", "provider": "github", "mode": "managed", "state": "<random>", "nonce": "<random>" },
    { "type": "passkey", "action": "authenticate", "options": { "...PublicKeyCredentialRequestOptionsJSON with rpId = the sign-in address hostname..." } }
  ] }

→ { id, browser_url, expires_at }. Send the browser to browser_url. Keep id on the server: it is the secret that collects the result, and it never appears in the browser URL. id and the page token in browser_url are opaque strings; do not parse them.

Rules: state values unique per transaction and 16-128 characters; BYO authorize_url must be https and, for known providers, on that provider's authorization host; ttl_sec ≤ 600; return_url must be on the service's configured app URL origin, otherwise the app URL itself is used. A service with no app URL cannot create transactions (app_url_missing).

GET /v1/transactions/<id> → { status: 'pending', retry_after } | { status: 'completed', method: <index>, result } | { status: 'consumed' | 'cancelled' | 'expired' }. A completed result is returned once; the row is then consumed and the payload deleted. That delivery ends the transaction: nothing further is required from the service, and it is what the dashboard counts as a sign-in.

result:

  • BYO OAuth: { type:'oauth', mode:'byo', provider, state, code }. Exchange code at the provider's token_endpoint with your client and PKCE verifier; verify the ID token (issuer, audience = your client id, nonce, signature via jwks_uri) or fetch userinfo_endpoint for oauth2 providers.
  • Managed OAuth: { type:'oauth', mode:'managed', provider, state, id_token?, access_token?, token_type? }. Verify id_token with audience = managed_client_id and your nonce; for oauth2 providers call userinfo_endpoint with access_token. The relay discards tokens after delivery.
  • Passkey: { type:'passkey', action, response } — the raw RegistrationResponseJSON or AuthenticationResponseJSON. Verify with a WebAuthn library using the sign-in address as expected origin, its hostname as RP ID, and your stored challenge and credential public keys.

The browser page offers Cancel (transaction → cancelled, browser → return URL) and expires with the transaction. After completion the browser is redirected to the return URL.

Custom domains

A service may add one custom hostname (CNAME auth.example.com → <slug>.login.mylogin.world.compas.cs.stonybrook.edu; a zone apex uses A records to the sites' IPs, which do not follow failover). The slug address keeps working; the host a transaction is created for is the host its page uses: POST /v1/services/<hostname>/transactions (a path segment containing a dot is a hostname) returns a browser_url on https://<hostname>. On a custom host:

  • managed providers work as on the slug address: their callback stays https://login.mylogin.world.compas.cs.stonybrook.edu/oauth/cb, which returns the browser to the page on the hostname, and the consent screen names the relay;
  • your own OAuth clients register https://<hostname>/oauth/cb;
  • passkeys use the hostname as RP ID and origin, so passkeys made on the slug address do not work there, nor the reverse.

The SDK takes the host from AUTH_RELAY_URL=https://<hostname>.

What the relay never does

Store identities, credentials, or tokens beyond delivery; decide who may sign in; connect to the service; verify a login on the service's behalf; send the browser anywhere except the service's configured app URL.