Skip to content

Authentication API

This document provides comprehensive information about the authentication endpoints of the FastAPI RBAC API. It is fully aligned with the backend implementation as of July 2025.


Overview

  • All endpoints are under /api/v1/auth.
  • Most endpoints are public, but some require authentication (e.g., logout, change password).
  • All responses are wrapped in a data field and may include a message.
  • Error responses follow the standard error format with appropriate HTTP status codes.
  • CSRF protection is required for state-changing operations (see CSRF section).

Endpoints

POST /api/v1/auth/login

Log in a user with email and password.

Request Body:

{
  "email": "user@example.com",
  "password": "secure_password"
}

Response:

{
  "data": {
    "access_token": "...",
    "token_type": "bearer",
    "user": { "...": "..." }
  },
  "message": "Login successful"
}

Sets an HttpOnly refresh_token cookie (path /api/v1/auth). The refresh token is not returned in the JSON body for the SPA.

Error Responses:

  • 422 Unprocessable Entity: Invalid credentials, locked account, unverified email
  • 400 Bad Request: Input sanitization failed

POST /api/v1/auth/register

Register a new user (self-service, public).

Request Body:

{
  "email": "newuser@example.com",
  "password": "SecurePassw0rd!47",
  "first_name": "Jane",
  "last_name": "Smith"
}

The password must satisfy the complexity policy in settings -- the same policy the password-reset and change-password endpoints apply. SecurePassword123! would be rejected for the sequential run 123.

Response:

{
  "data": {
    "id": "uuid",
    "email": "newuser@example.com",
    "first_name": "Jane",
    "last_name": "Smith",
    "is_active": true,
    "verified": false,
    ...
  },
  "message": "Registration successful. Please check your email to verify your account."
}

Error Responses:

  • 400 Bad Request: Invalid input, password too long, rate limit exceeded, or a password that fails the complexity policy. A complexity failure answers with an object rather than a string:
{
  "detail": {
    "message": "Password does not meet complexity requirements.",
    "errors": ["Password must be at least 12 characters long"]
  }
}

POST /api/v1/auth/verify-email

Verify a user's email address using a verification token.

Request Body:

{
  "token": "verification_token"
}

Response:

{
  "data": { ...user fields... },
  "message": "Email verified successfully."
}

A second call with the same still-valid JWT, after the Redis key has been consumed, returns 200 with "Email is already verified." when the account is active and already verified (#239). The token remains single-use: the Redis key is deleted on first success.

Error Responses:

  • 400 Bad Request: "This verification link is invalid or has expired. Please request a new verification email and try again." — returned identically for an unknown address, a disabled account (including disabled-and-verified), a still-unverified user whose Redis token is missing or does not match, and a wrong or expired token, so the response never confirms that an account exists (#137). The audit log records which it was.
  • 401 Unauthorized: the token itself failed JWT validation

POST /api/v1/auth/resend-verification-email

Resend the verification email to a user (rate-limited).

Request Body:

{
  "email": "user@example.com"
}

Response:

{
  "message": "If the email exists and the account is not verified, a verification email has been sent."
}

Error Responses:

  • 429 Too Many Requests: Rate limit exceeded
  • 400 Bad Request: Invalid email

POST /api/v1/auth/logout

End the calling session: revoke that refresh token and every access token that shares its session id, then delete the HttpOnly refresh_token cookie. Other sessions on the account stay usable. Requires CSRF (cookie-authenticated mutation).

Session identity comes from the refresh cookie when present; if the cookie is missing, from the access token's allowlist metadata. If neither yields a session id, the request fails and does not revoke other sessions.

Request Headers:

Authorization: Bearer <access_token>
X-CSRF-Token: <csrf_token>

Response:

{
  "message": "Successfully logged out"
}

Permissions: Authenticated user

Error Responses:

  • 400 Bad Request: Unable to identify the current session
  • 401 Unauthorized: Not authenticated

POST /api/v1/auth/logout/all

Revoke every session for the authenticated user (every allowlist token, of any type) and delete the HttpOnly refresh_token cookie. Same CSRF and authentication rules as logout. Change-password still calls the revocation primitive directly rather than this route. The first-party SPA posts this from Log out everywhere after confirmation.

Request Headers:

Authorization: Bearer <access_token>
X-CSRF-Token: <csrf_token>

Response:

{
  "message": "Successfully logged out from all sessions"
}

Permissions: Authenticated user


POST /api/v1/auth/change_password

Change the current user's password (authenticated).

Request Headers:

Authorization: Bearer <access_token>

Request Body:

{
  "current_password": "OldPassword123!",
  "new_password": "NewPassword456!"
}

Response:

{
  "data": {
    "access_token": "...",
    "token_type": "bearer",
    "user": { "...": "..." }
  },
  "message": "Password changed successfully."
}

Re-issues the HttpOnly refresh_token cookie (refresh token omitted from JSON).

Error Responses:

  • 400 Bad Request: Invalid current password, password complexity, password reuse
  • 401 Unauthorized: Not authenticated

POST /api/v1/auth/password-reset/request

Request a password reset email (public, rate-limited).

Request Body:

{
  "email": "user@example.com"
}

Response:

{
  "message": "If the email exists and the account is active, a password reset link has been sent."
}

The same 200 and the same message are returned for an unknown address, a disabled account, and an active one (#137). Only MODE=development differs, where the reset token is echoed back for MailHog.

Error Responses:

  • 400 Bad Request: Invalid email
  • 429 Too Many Requests: Rate limit exceeded

POST /api/v1/auth/password-reset/confirm

Reset a user's password using a reset token.

Request Body:

{
  "token": "reset_token",
  "new_password": "NewPassword456!"
}

Response:

{
  "message": "Password has been reset successfully."
}

Error Responses:

  • 400 Bad Request: "This password reset link is invalid or has expired. Please request a new password reset and try again." — returned identically for an unknown address, a disabled account, and a token that is wrong, expired or not allow-listed (#137)
  • 400 Bad Request: password complexity or password-history failures, which describe the submitted password and stay distinct

POST /api/v1/auth/new_access_token

Refresh an access token. Prefer the HttpOnly refresh_token cookie (first-party SPA). Requires CSRF. Optional JSON body refresh_token is a documented fallback for non-browser API clients.

Cookie: refresh_token (HttpOnly; path scoped to /api/v1/auth)

Request Body (optional):

{
  "refresh_token": "..."
}

Response:

{
  "data": {
    "access_token": "...",
    "token_type": "bearer"
  },
  "message": "Access token generated correctly"
}

Error Responses:

  • 401 Unauthorized: Missing, invalid, or expired refresh token
  • 403 Forbidden: CSRF failure or refresh token not on Redis allowlist
  • 403 Forbidden: the session was revoked because this refresh came from a different origin network than the one it was established from (VALIDATE_TOKEN_IP, ADR 0011 decision 5). The body is identical to the allowlist rejection above, deliberately: the response does not tell a caller that the address is what gave it away

GET /api/v1/auth/csrf-token

Get a CSRF token for use in state-changing operations (required for POST/PUT/DELETE).

Response:

{
  "data": { "csrf_token": "..." },
  "message": "CSRF token generated successfully"
}

Error Responses

  • 400 Bad Request: Invalid input, password complexity, invalid token
  • 401 Unauthorized: Missing or invalid token
  • 403 Forbidden: Not allowed
  • 404 Not Found: User not found
  • 409 Conflict: Email already registered
  • 422 Unprocessable Entity: Validation errors, locked account, unverified email
  • 429 Too Many Requests: Rate limit exceeded
  • 500 Internal Server Error: Unexpected error

Security Notes

  • All state-changing endpoints require a valid CSRF token (see /csrf-token).
  • Passwords must meet complexity requirements and cannot be reused (see backend settings).
  • Rate limits apply to registration, password reset, and verification email endpoints.
  • Tokens are JWTs and must be included in the Authorization header as Bearer <token>.
  • Always use HTTPS in production.

See Also