Starts Cognito Hosted UI authorization code + PKCE for an explicitly allowlisted first-party tenant and HTTPS origin. The canonical tenant UUID in the route selects one exact redirect/post-login/logout tuple; the request Host and Origin validate that choice; neither selects it. callback validates signed issuer/audience/nonce/signature claims and refuses any subject without an active operator binding. Invalid, duplicate, mismatched, unlisted, or absent configuration returns a typed 503 end_user_auth_not_configured before storage; it never falls back to email or an app credential.
| Method and path | Authority | Result |
GET .../login | Configured first-party host | 302 to Cognito with state, nonce, and S256 PKCE challenge. |
GET .../callback | Signed login cookie + one-use Cognito code | 302 to configured app and opaque HttpOnly session cookie; no Cognito token is retained. |
POST .../session-token | Exact first-party Origin + active session cookie | 201 with a 600-second HS256 Bearer JWT after fresh People & Orgs authorization. No aud. |
POST /platform/tenants/{tenantId}/token-context | The issued end-user JWT as Authorization: Bearer | 200 no-store verified subject, roles, scopes, person/relationship claims, and expiry; never echoes the token. |
POST .../logout | Exact first-party Origin + optional session cookie | Revokes local session, clears cookie, and redirects through Cognito logout. |
The auth URL, identity binding, local session, and audit always use the canonical active Platform tenant UUID. At session-token, Platform loads that tenant row and signs its existing unique platform.tenant.tenant_key into downstream tenantId/tenant_id because People & Orgs and Results already route and store by tenant key. This bounded handoff adds no repository-wide UUID adapter, additional tenant claim, or second mapping table. Human tokens carry only exact people/relationship ids—personIds contains authorized students, never the parent or guide actor, and there are no place, school, or class container grants. A role with no eligible student scope fails closed.
Operator configuration is explicit: shared PLATFORM_TIMEBACK_COGNITO_ISSUER, PLATFORM_TIMEBACK_COGNITO_HOSTED_DOMAIN, public PLATFORM_TIMEBACK_COGNITO_CLIENT_ID, PEOPLE_ORGS_BASE_URL, and the existing PLATFORM_JWT_SIGNING_SECRET; plus PLATFORM_TIMEBACK_TENANT_ROUTES_JSON, a strict array whose entries contain exactly tenantId, redirectUri, postLoginUri, and logoutUri. Every URI in one entry shares the same HTTPS origin and the callback path contains that entry's canonical tenant UUID. The legacy single-tenant URI variables remain supported only when the allowlist is absent. Platform mints the bounded People & Orgs service token from the existing signing key; there is no second long-lived service-token setting.
Each consumer reverse-proxies only its exact Platform auth paths on its own public origin. The session cookie therefore remains host-only and SameSite, and the app receives no issuer credential, signing secret, or cross-site cookie. Both callback URLs must be registered on the shared Cognito public client.
A server-side consumer such as AP One keeps the JWT opaque and calls POST /platform/tenants/{canonicalTenantUuid}/token-context with it as the Bearer credential. Platform reuses its existing JWT verifier and resolves that canonical UUID only to the active row's existing tenant key. The raw token must contain both tenantId and tenant_id, exactly equal to that key; workspaceId/workspace_id cannot rescue or broaden tenant scope. Raw roles and scopes are unique nonempty string arrays, scalar role equals roles[0], scopes are exactly people_orgs:read and results:read, and integer exp - iat is exactly 600. Parent/guardian relationships require a parent or guardian role; guide relationships require the guide role; legitimate multi-role tokens remain valid. Unknown, container-grant, and extra privilege claims fail closed. The endpoint mints nothing, stores nothing, never echoes the token, and does not give the app the symmetric signing key.
Exact 200 response. Only relationship fields applicable to the verified roles are present; personIds is the exact union of authorized student ids.
{
"active": true,
"tokenType": "end_user",
"tenantId": "<tenant_key>",
"tenant_id": "<same tenant_key>",
"platformTenantId": "<canonical tenant UUID>",
"subject": "timeback:cognito:<32 lowercase hex>",
"roles": ["student"],
"scopes": ["people_orgs:read", "results:read"],
"issuedAt": 123,
"expiresAt": 723,
"claims": {
"personId": "<actor person id>",
"studentId": "<student actor only; otherwise omitted>",
"studentIds": ["<student actor only; otherwise omitted>"],
"agentOf": ["<parent/guardian student; otherwise omitted>"],
"guardianOf": ["<same ids as agentOf; otherwise omitted>"],
"guideOf": ["<guide student; otherwise omitted>"],
"personIds": ["<exact authorized-student union>"],
"authProvider": "timeback-cognito"
}
}
| Status | Code | Boundary |
| 400 | invalid_request | Route tenant is not a canonical Platform UUID. |
| 401 | unauthorized | Bearer is missing, malformed, tampered, wrong-issuer, expired, missing/malformed exp, future-issued, or has neither tenant alias. |
| 403 | tenant_scope_mismatch | A signed token has a missing, conflicting, or wrong tenant alias, or includes a workspace alias. |
| 403 | forbidden | Tenant is inactive, or the signed token fails the exact end-user role, scope, lifetime, person-scope, relationship-provenance, or claim allowlist contract. |
| 404 | not_found | Canonical tenant UUID does not exist. |
| 405 | method_not_allowed | An authenticated request uses a method other than POST; Allow: POST. |
| Tenant | Canonical UUID | Initial first-party route tuple |
alphatimeback3 | 5ebda6a8-91f1-4685-a055-146c035b9e22 | https://alphatimeback3-andymontgomery-9773s-projects.vercel.app; callback under /platform/tenants/5ebda6a8-91f1-4685-a055-146c035b9e22/auth/timeback/callback, then /home; logout to /sign-in. |
AP One is intentionally absent until its production app origin exists and the active tenant create is read back. Its deterministic onboarding request is expected to return 723c3d8b-274a-4f6c-8b46-df92951348eb, but that reservation is not evidence of a live tenant or route tuple. https://apone.inceptstore.com is the current staging review origin and must not be used in the production allowlist or real-student callback path.