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 }. Exchangecodeat the provider'stoken_endpointwith your client and PKCE verifier; verify the ID token (issuer, audience = your client id, nonce, signature viajwks_uri) or fetchuserinfo_endpointforoauth2providers. - Managed OAuth:
{ type:'oauth', mode:'managed', provider, state, id_token?, access_token?, token_type? }. Verifyid_tokenwith audience =managed_client_idand yournonce; foroauth2providers calluserinfo_endpointwithaccess_token. The relay discards tokens after delivery. - Passkey:
{ type:'passkey', action, response }— the rawRegistrationResponseJSONorAuthenticationResponseJSON. 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.