`POST /auth/session` — exchange browser-minted Cognito tokens for the session cookies. The **only** route that issues them.
/auth/sessionVerification is the whole endpoint
[authenticate_bearer] runs signature, iss, exp, aud, token_use,
the closed trusted-issuer set, the org-header match and the #317
session-revocation check, with require_cognito — you exchange an
identity-provider session for our cookies, never a self-issued OAuth token.
Without it this endpoint would set a session cookie from an attacker-chosen
string.
require_acting_team is false: the client learns its teams from this
response, so demanding an acting team to sign in is unsatisfiable. Same arm
GET /users/me takes.
⚠️ Login CSRF / session fixation, and the four things that stop it
The attack is forcing a victim's browser to POST an attacker's tokens here, so the victim then works inside the attacker's session — for a log product, the victim's queries landing in the attacker's org with the audit trail naming the attacker's user. Four controls block it, and the first two are the ones a refactor could quietly remove:
Jsonrefuses a cross-site form post. An HTML<form>can only sendapplication/x-www-form-urlencoded,multipart/form-dataortext/plain; axum'sJsonextractor answers 415 to all three. Accepting form encoding here — or hand-parsing the body — deletes this defence.a_form_encodable_post_is_refusedis the guard;- the API has no CORS layer, so a cross-origin
fetch(..., {credentials: 'include'})fails CORS and the browser discards this response'sSet-Cookie(rh-jculnc); - the CSRF layer refuses this route like any other —
rh-cl95c's predicate keys onSec-Fetch-Site, not on a session cookie, precisely so it covers the route that creates the session (a cookie-gated check could not, by construction); __Host-means only script on the victim's own origin could plant a cookie directly, which is already game over for other reasons.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
POST /auth/session's request body — the tokens the browser just minted.
⚠️ No id_token, deliberately. The design listed one; nothing reads it.
Its only former job was to be decoded by the app for the principal, which is
what [SessionResponse] now carries — and rh-cl95c R1 says the id token is
never stored anywhere. A credential the server accepts and discards is a
credential that shows up in a request log or an error capture for no
benefit, so the field is absent rather than ignored.
⚠️ No Debug, deliberately. Both fields are live credentials, and a
Debug impl is how one ends up in a tracing line or a panic message.
Response Body
application/json
application/json
curl -X POST "https://example.com/auth/session" \ -H "Content-Type: application/json" \ -d '{ "access_token": "string", "refresh_token": "string" }'{ "organization_slug": "string", "user": { "avatar": "string", "created_at": "string", "email": "string", "id": "string", "is_admin": true, "kind": "native", "last_login_at": "string", "name": "string", "org_id": "string", "provisioned_by": "string", "role": null, "status": null, "status_unrecognized": "string", "team_ids": [ "string" ], "updated_at": "string" }}`POST /auth/refresh` — renew the access token from the refresh cookie. POST
The credential arrives in `__Host-vf_refresh`, not in the body: page script cannot read an `HttpOnly` cookie, so it cannot put the token there any more. This handler reads the jar and builds the port's [`RefreshInput`] from `{cookie, body.slug}` — the port and `refresh_for` are untouched, and know nothing about cookies. ⚠️ **Only `vf_session` and `vf_expires_at` are re-set.** Cognito's `REFRESH_TOKEN_AUTH` returns no new refresh token, so there is nothing to re-emit; the browser keeps the `vf_refresh` it has and a second renewal works. Re-setting it from `RefreshInput.refresh_token` would work too, but only by accident — it would write the same value back, and the first time Cognito *did* rotate, a handler that re-set the old value would pin the session to a dead credential. A missing cookie is the same uniform 401 as a bad one. #314's anti-enumeration property is that every probe-able failure is indistinguishable, and "you sent no cookie" is exactly such a probe.
`POST /auth/webauthn/finish` — answer the challenge with the browser's assertion and, on success, issue the session exactly as `/auth/session` does ([`establish_session`]) minus the MFA gate: a user-verified WebAuthn sign-in is the strong factor. POST
All four of `/auth/session`'s login-CSRF controls apply unchanged — `Json` only, no CORS layer, the `Sec-Fetch-Site` CSRF layer and `__Host-` cookies — and the tokens are minted server-side, so a caller cannot choose them.