Files
fm_be/docs/auth-flow.md
2026-07-16 22:16:45 +07:00

7.1 KiB

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:

{
  "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:

{
  "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:

{
  "data": {
    "type": "auth_reset_password",
    "attributes": {
      "token": "<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: <one-time-token>

auth/pin/verify menggunakan action berupa UUID opaque (transaction ID), bukan permission key seperti user.delete.

Detailed flow + full payload examples:

2) TOTP Setup (Enable 2FA)

2.1 Setup

POST /api/v1/auth/totp/setup

Request:

{
  "data": {
    "type": "auth_totp_setup",
    "attributes": {
      "user_id": "<uuid>",
      "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:

{
  "data": {
    "type": "auth_totp_confirm",
    "attributes": {
      "user_id": "<uuid>",
      "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:

{
  "data": {
    "type": "auth_totp_verify",
    "attributes": {
      "challenge_token": "<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

POST /api/v1/auth/sso/link

Body:

{
  "data": {
    "type": "auth_sso_link",
    "attributes": {
      "provider": "microsoft",
      "code": "<microsoft_authorization_code>"
    }
  }
}

Rules:

  • 1 user only 1 SSO mapping.
  • Same SSO account cannot be linked to another user.

DELETE /api/v1/auth/sso/unlink

Body:

{
  "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.