Vigilfield Docs
API referenceAuth

`POST /auth/session` — exchange browser-minted Cognito tokens for the session cookies. The **only** route that issues them.

POST/auth/session

Verification 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:

  1. Json refuses a cross-site form post. An HTML <form> can only send application/x-www-form-urlencoded, multipart/form-data or text/plain; axum's Json extractor answers 415 to all three. Accepting form encoding here — or hand-parsing the body — deletes this defence. a_form_encodable_post_is_refused is the guard;
  2. the API has no CORS layer, so a cross-origin fetch(..., {credentials: 'include'}) fails CORS and the browser discards this response's Set-Cookie (rh-jculnc);
  3. the CSRF layer refuses this route like any other — rh-cl95c's predicate keys on Sec-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);
  4. __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.