7.1 KiB
Security PIN Flow
This document explains how to configure and use a per-user 6-digit Security PIN for sensitive actions. All API payloads follow JSON:API.
1) Setup Security PIN (first time)
Endpoint:
POST /api/v1/auth/pin/setup
Auth:
- Requires logged-in session (
AUTHcookie / access token cookie)
Request:
{
"data": {
"type": "auth_pin_setup",
"attributes": {
"pin": "123456",
"confirm_pin": "123456"
}
}
}
Success (200):
{
"data": {
"type": "auth_pin_setup",
"attributes": {
"configured": true
}
}
}
If PIN already exists (409):
{
"errors": [
{
"status": "409",
"title": "PIN already configured",
"detail": "security PIN is already set"
}
]
}
2) Verify PIN before sensitive action
Endpoint:
POST /api/v1/auth/pin/verify
Auth:
- Requires logged-in session
Request:
{
"data": {
"type": "auth_pin_verify",
"attributes": {
"pin": "123456",
"action": "a546de2f-f1e6-4c2c-ab03-9b4bf39f4d74"
}
}
}
Success (200):
{
"data": {
"type": "auth_pin_verify",
"attributes": {
"verified": true,
"action_token": "<one-time-token>",
"action_token_ttl_ms": 300000
}
}
}
Use the returned token on the sensitive API call:
- Header:
X-PIN-Verification-Token: <one-time-token>
Example sensitive request:
DELETE /api/v1/users/delete/{uuid}
X-PIN-Verification-Token: <one-time-token>
Cookie: wucher_at=...
Important:
- Token is action-bound (
a546de2f-f1e6-4c2c-ab03-9b4bf39f4d74only for that action). - Token is one-time use.
- Token expires by
AUTH_SECURITY_PIN_ACTION_TOKEN_TTL. - Maximum failed attempts follow
AUTH_SECURITY_PIN_MAX_ATTEMPTS(default5). - When max attempts are reached, API returns
429and PIN becomes blocked. - Block is permanent until PIN reset succeeds (
AUTH_SECURITY_PIN_LOCK_DURATION=0means no auto-unlock timeout).
3) Change Security PIN
Endpoint:
POST /api/v1/auth/pin/change
Auth:
- Requires logged-in session
Request:
{
"data": {
"type": "auth_pin_change",
"attributes": {
"current_pin": "123456",
"new_pin": "654321",
"confirm_pin": "654321"
}
}
}
Success (200):
{
"data": {
"type": "auth_pin_change",
"attributes": {
"updated": true
}
}
}
4) Reset Security PIN Flow
4.1 Request reset link (blocked PIN button)
Endpoint:
POST /api/v1/auth/pin/request-reset
Auth:
- Requires logged-in session
Request:
{
"data": {
"type": "auth_pin_request_reset",
"attributes": {
"password": "<current-account-password>"
}
}
}
Success (200):
{
"data": {
"type": "auth_pin_request_reset",
"attributes": {
"message": "If allowed, a security PIN reset link has been sent to your email."
}
}
}
If PIN not blocked (409):
{
"errors": [
{
"status": "409",
"title": "PIN not blocked",
"detail": "security PIN is not blocked"
}
]
}
Invalid password (401):
{
"errors": [
{
"status": "401",
"title": "Unauthorized",
"detail": "invalid password"
}
]
}
Rate limited (429):
{
"errors": [
{
"status": "429",
"title": "Too many requests",
"detail": "please wait before requesting another reset link"
}
]
}
4.2 Request reset link (public forgot endpoint)
Endpoint:
POST /api/v1/auth/pin/forgot
Request:
{
"data": {
"type": "auth_forgot_pin",
"attributes": {
"email": "user@example.com"
}
}
}
Response is always generic (200):
{
"data": {
"type": "auth_forgot_pin",
"attributes": {
"message": "If the account exists, a security PIN reset link has been sent."
}
}
}
4.3 Reset PIN with password or Microsoft re-auth
Endpoint:
POST /api/v1/auth/pin/reset
Notes:
methodis optional. Default ispassword.- For
method=password,tokenis required and must be the raw single-use reset token from the email reset link, then sendpassword. - For
method=microsoft,tokenis not required. Sendreauth_tokenfrom Microsoft re-auth directly.
Request with password re-auth:
{
"data": {
"type": "auth_reset_pin",
"attributes": {
"token": "<raw-token-from-email-link>",
"password": "<current-account-password>",
"new_pin": "123456",
"confirm_pin": "123456"
}
}
}
Request with Microsoft re-auth:
{
"data": {
"type": "auth_reset_pin",
"attributes": {
"method": "microsoft",
"reauth_token": "<reauth-token-from-/auth/microsoft/reauth?purpose=pin_reset_request>",
"new_pin": "123456",
"confirm_pin": "123456"
}
}
}
Success (200):
{
"data": {
"type": "auth_reset_pin",
"attributes": {
"message": "Security PIN has been reset successfully.",
"reset_success": true,
"redirect_url": "https://app.example.com"
}
}
}
Invalid/expired token (400):
{
"errors": [
{
"status": "400",
"title": "Invalid reset link",
"detail": "This reset link is invalid or expired."
}
]
}
Invalid password (401):
{
"errors": [
{
"status": "401",
"title": "Unauthorized",
"detail": "invalid password"
}
]
}
5) Sensitive endpoints currently protected by PIN token
The following routes require X-PIN-Verification-Token:
POST /api/v1/auth/invite(action=2a59e8b4-5a72-4be3-b8fc-4f49941181c3)POST /api/v1/users/create(action=2a59e8b4-5a72-4be3-b8fc-4f49941181c3)PATCH /api/v1/users/update/:uuid(action=428fdf48-3f99-46ea-bc47-8fb2e312be08)DELETE /api/v1/users/delete/:uuid(action=a546de2f-f1e6-4c2c-ab03-9b4bf39f4d74)POST /api/v1/roles/create(action=7fb1d693-8692-4e8f-be86-fd0e29e96bc3)PATCH /api/v1/roles/update/:uuid(action=18ca5bbf-ce9d-4e24-8ace-793de56c1de0)DELETE /api/v1/roles/delete/:uuid(action=b87fca76-783d-45e7-9022-b2d9c95f6da7)POST /api/v1/roles/:uuid/permissions(action=48e523f8-d86f-4b24-b0e3-e213e4d33db4)DELETE /api/v1/roles/:uuid/permissions/:permission_uuid(action=f2bf59f8-0783-40fc-94ce-a79e40f84c44)POST /api/v1/roles/:uuid/permissions/assign-bulk(action=48e523f8-d86f-4b24-b0e3-e213e4d33db4)POST /api/v1/roles/:uuid/permissions/remove-bulk(action=f2bf59f8-0783-40fc-94ce-a79e40f84c44)
6) Security controls
- PIN must be exactly 6 digits.
- PIN is encrypted at rest (
users.security_pin_encrypted), not stored in plaintext. - Forgot/reset tokens are random, hashed, single-use, and expiring.
- Verify attempts are rate-limited per user with lockout (
AUTH_SECURITY_PIN_MAX_ATTEMPTS,AUTH_SECURITY_PIN_LOCK_DURATION). - Forgot endpoint response is generic to reduce account enumeration risk.
7) Environment variables
AUTH_SECURITY_PIN_RESET_URLAUTH_SECURITY_PIN_RESET_TTLAUTH_FORGOT_SECURITY_PIN_EMAIL_COOLDOWNAUTH_FORGOT_SECURITY_PIN_MIN_DURATIONAUTH_SECURITY_PIN_MAX_ATTEMPTSAUTH_SECURITY_PIN_LOCK_DURATIONAUTH_SECURITY_PIN_ACTION_TOKEN_TTL