Server-side contract
Your server exposes four small routes and calls one route on ours. There is no library to install: this page is the full contract, followed by an outline of the logic. Complete, tested servers in Node.js and Python are in Complete server examples, and every request and response is shown in Request and response examples.
Routes your server must provide#
| Route | Does |
|---|---|
GET /api/config |
Returns {"shell_origin": "<web shell origin>"} so the page knows who to talk to in the browser |
POST /api/nonce |
Creates a random nonce (at least 24 bytes), stores it for 2 minutes, returns {"nonce": "..."} |
POST /api/session |
Body {token, nonce}. Removes the nonce first (so it works once), calls the verify route below, then returns {"session": "...", "sub": "...", "name": "..."} |
every other /api/* |
Requires Authorization: Bearer <session>; answer 401 otherwise. Sessions can live in memory for about an hour |
The route you call on SuperPreneur#
POST {IDP}/api/launch/verify with HTTP Basic auth (client_id as user, client_secret as password) and a JSON body {"token": "...", "nonce": "..."}. The API reference has the full description.
| Status | Body | Meaning |
|---|---|---|
| 200 | {"sub": "...", "scopes": ["profile"], "name": "..."} |
Valid. name is present only if the profile scope was approved |
| 400 | {"error": "expired"} |
Token older than 60 seconds |
| 400 | {"error": "replayed"} |
Token already used |
| 400 | {"error": "nonce_mismatch"} |
The nonce does not match the one the token was issued for |
| 400 | {"error": "wrong_audience"} |
The token was issued for a different app |
| 400 | {"error": "invalid_token"} |
Bad signature or malformed token |
| 400 | {"error": "revoked"} |
The user revoked access, was deactivated, or the app is no longer allowed |
| 401 | {"error": "invalid_client"} |
Wrong client id or secret |
| 403 | {"error": "app_suspended"} |
The app has been suspended |
| 429 | More than 600 calls a minute |
Responses are marked Cache-Control: no-store. A rejected call does not use up a valid token, so a wrong nonce does not lock the user out.
What sub is. A stable id for this user in your app only. Use it as the key for the user's data (carts, bookings, scores). Never use the name as a key.
The login step in outline (any language)#
POST /api/nonce:
nonce = 24 random bytes, URL-safe Base64 without padding (32 characters)
nonces[nonce] = now + 120 seconds
return {nonce}
POST /api/session with {token, nonce}:
if token is empty or nonce does not match ^[A-Za-z0-9_-]{16,128}$ : return 400 bad_request
expiry = nonces.remove(nonce) # remove FIRST, in one atomic step
if there was none, or it has expired: return 400 unknown_or_expired_nonce
reply = POST {IDP}/api/launch/verify # Basic auth client_id:client_secret, 5 second timeout
body {token, nonce}
if reply.status != 200: return 401 {error: reply.error}
session = 32 random bytes, URL-safe Base64
sessions[session] = {sub, name, expires = now + 1 hour}
return 200 {session, sub, name}
every other /api/* route:
user = sessions[Bearer token from the Authorization header], if not expired
if no user: return 401 {error: no_session}
... key everything you store by user.sub
Nonces and sessions can live in memory on a single server. With several servers, keep them in a shared store such as Redis (see Generating the nonce).