Vigilfield Docs
API referenceAuth

`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/auth/webauthn/finish

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.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

POST /auth/webauthn/finish's body. No Debug: session is a live challenge handle.

Response Body

application/json

curl -X POST "https://example.com/auth/webauthn/finish" \  -H "Content-Type: application/json" \  -d '{    "credential": "string",    "email": "string",    "session": "string",    "slug": "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/session` — exchange browser-minted Cognito tokens for the session cookies. The **only** route that issues them. POST

# 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.

`POST /auth/webauthn/start` — begin a passkey / security-key sign-in (`rh-kk6vbu`). Anonymous; the server runs Cognito's `USER_AUTH` flow with `PREFERRED_CHALLENGE=WEB_AUTHN` on the org's app client. POST

Every "no challenge" outcome — no key registered, unknown user, unknown org — is one 400 with one sentence, so this is no better an account oracle than the pool's `preventUserExistenceErrors` already allows. `Json` only, like `/auth/session` and for the same login-CSRF reason: a form post is a 415 before the handler runs.