# Auth Flow This document describes the recommended authentication flows in Wucher. All API responses follow **JSON:API**. ## 1) Email/Password Registration ### 1.1 Register `POST /api/v1/auth/register` Notes: - The user role is assigned based on `AUTH_DEFAULT_ROLE`. - For production, you can disable this endpoint by setting `AUTH_DISABLE_REGISTER=true`. Request: ```json { "data": { "type": "auth_register", "attributes": { "email": "user@example.com", "password": "StrongP@ssw0rd", "first_name": "John", "last_name": "Doe", "mobile_phone": "" } } } ``` Response: `201 Created` with user resource. ### 1.2 Verify Email User receives a verification link in email: `GET /api/v1/auth/verify-email?token=...` Response: `200 OK` with `{ verified: true }`. ### 1.3 Login `POST /api/v1/auth/login` If TOTP **is not enabled**, response is `200 OK` and cookies are set: - `wucher_at` (access) - `wucher_rt` (refresh) If TOTP **is enabled**, response is `202 Accepted` with `challenge_token`. ### 1.4 Forgot Password `POST /api/v1/auth/forgot-password` Request: ```json { "data": { "type": "auth_forgot_password", "attributes": { "email": "user@example.com" } } } ``` Response is always `200 OK` with a generic message: `If the account exists, a password reset link has been sent.` ### 1.5 Reset Password `POST /api/v1/auth/reset-password` Request: ```json { "data": { "type": "auth_reset_password", "attributes": { "token": "", "new_password": "StrongPassphrase123!", "confirm_password": "StrongPassphrase123!" } } } ``` Success response: `200 OK` with message instructing the user to log in again. Invalid/expired/used token response: `400 Bad Request` with: `This reset link is invalid or expired.` ### 1.6 Security PIN for Sensitive Actions Security PIN endpoints: - `POST /api/v1/auth/pin/setup` - `POST /api/v1/auth/pin/verify` - `POST /api/v1/auth/pin/change` - `POST /api/v1/auth/pin/forgot` - `POST /api/v1/auth/pin/reset` For `POST /api/v1/auth/pin/reset`: - If `method=password`, send the reset `token` from the email link plus `password`. - If `method=microsoft`, send only `reauth_token` from Microsoft re-auth. Sensitive actions require header: - `X-PIN-Verification-Token: ` `auth/pin/verify` menggunakan `action` berupa UUID opaque (transaction ID), bukan permission key seperti `user.delete`. Detailed flow + full payload examples: - See [docs/security-pin.md](security-pin.md) ## 2) TOTP Setup (Enable 2FA) ### 2.1 Setup `POST /api/v1/auth/totp/setup` Request: ```json { "data": { "type": "auth_totp_setup", "attributes": { "user_id": "", "email": "user@example.com" } } } ``` Response: `200 OK` with `secret` and `otpauth_url`. > TOTP is **not** enabled yet (pending). ### 2.2 Confirm `POST /api/v1/auth/totp/confirm` Request: ```json { "data": { "type": "auth_totp_confirm", "attributes": { "user_id": "", "code": "123456" } } } ``` Response: `200 OK` and TOTP becomes **enabled**. ## 3) TOTP Login (2FA) ### 3.1 Login `POST /api/v1/auth/login` Response: `202 Accepted` with `challenge_token`. ### 3.2 Verify `POST /api/v1/auth/totp/verify` Request: ```json { "data": { "type": "auth_totp_verify", "attributes": { "challenge_token": "", "code": "123456" } } } ``` Response: `200 OK` and cookies are set. ## 4) Microsoft Entra SSO ### 4.1 Start Login `GET /api/v1/auth/microsoft/login` Response: `200 OK` with JSON:API body (`type: auth_microsoft_url`) containing `url` and `redirect_url` ### 4.2 Callback Microsoft redirects to: `GET /api/v1/auth/microsoft/callback?code=...&state=...` Backend behavior: - If SSO mapping (`provider + provider_subject`) is linked to an existing user, login succeeds. - If mapping is missing, login is rejected (`401 SSO not linked`). - If mapping exists but user record is missing, login is rejected (`404 user not found`). - No auto-register on SSO callback. On success, backend sets cookies and may redirect to: `AUTH_SSO_SUCCESS_REDIRECT` ### 4.3 Link SSO (authenticated user) `POST /api/v1/auth/sso/link` Body: ```json { "data": { "type": "auth_sso_link", "attributes": { "provider": "microsoft", "code": "" } } } ``` Rules: - 1 user only 1 SSO mapping. - Same SSO account cannot be linked to another user. ### 4.4 Unlink SSO (authenticated user) `DELETE /api/v1/auth/sso/unlink` Body: ```json { "data": { "type": "auth_sso_unlink", "attributes": { "provider": "microsoft" } } } ``` ## 5) WebAuthn (Passkey) ### 5.1 Register Credential (Authenticated User) 1. `POST /api/v1/auth/webauthn/register/begin` (auth required) 2. Browser executes `navigator.credentials.create(...)` using `public_key` from response 3. `POST /api/v1/auth/webauthn/register/finish` with `challenge_token` + `credential` ### 5.2 Login with WebAuthn 1. `POST /api/v1/auth/webauthn/login/begin` with email 2. Browser executes `navigator.credentials.get(...)` using `public_key` from response 3. `POST /api/v1/auth/webauthn/login/finish` with `challenge_token` + `credential` 4. On success, backend sets auth cookies. ## 6) Refresh & Logout ### Refresh `POST /api/v1/auth/refresh` Uses refresh cookie to issue new tokens. ### Logout `POST /api/v1/auth/logout` Revokes refresh token and clears cookies. Frontend note: - Use `credentials: 'include'` on requests that depend on auth cookies. - Keep app logout local-only; do not redirect to Microsoft logout from this endpoint. - Microsoft front-channel logout is handled separately at `/api/v1/auth/microsoft/front-channel-logout` and is designed to work in an iframe. ## Notes - Access token is stored in HttpOnly cookie: `AUTH_JWT_ACCESS_COOKIE` - Refresh token is stored in HttpOnly cookie: `AUTH_JWT_REFRESH_COOKIE` - Cookies are configured by `AUTH_JWT_COOKIE_*` envs - Microsoft SSO sessions use `SameSite=None; Secure` so front-channel iframe logout can receive cookies. - Access token TTL is controlled by `AUTH_JWT_ACCESS_TTL` (default `10m`) - Refresh token TTL is controlled by `AUTH_JWT_REFRESH_TTL` (default `1h`) - TOTP challenge tokens are stored server-side in MySQL-backed runtime storage for `AUTH_TOTP_CHALLENGE_TTL` - Password-reset tokens are stored in MySQL as SHA-256 hashes (raw token is never stored) - Password-reset tokens are single-use and expire by `AUTH_PASSWORD_RESET_TTL` (default 20m) - Forgot-password endpoint has per-IP and per-email throttling - Login (including SSO callback and post-TOTP token issue) bumps per-user session version, so a new login invalidates previous active sessions - Successful password reset revokes active sessions by bumping per-user session version - PIN can be blocked after max failed attempts; use `POST /api/v1/auth/pin/request-reset` (auth required) to request reset email for blocked accounts - WebAuthn challenge token is single-use, stored server-side, and expires by `AUTH_WEBAUTHN_CHALLENGE_TTL`. - WebAuthn credential verification enforces RP ID + RP origin checks from `AUTH_WEBAUTHN_RP_ID` and `AUTH_WEBAUTHN_RP_ORIGINS`.