Skip to content

Authentication API

Authentication is handled by django-allauth in headless mode, wrapped in django-ninja-extra controllers. All auth endpoints are under /api/v1/auth/.

Session Management

Get Session

Check the current authentication state.

GET /api/v1/auth/session

Response (authenticated):

{
  "status": 200,
  "data": {
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "admin@acme.com",
      "username": null
    },
    "method": {
      "method": "password",
      "at": 1706817600,
      "email": "admin@acme.com"
    }
  }
}

Response (unauthenticated):

{
  "status": 401,
  "data": {
    "flows": [
      {"id": "login"},
      {"id": "signup"}
    ]
  }
}

Login

POST /api/v1/auth/login

Headers:

Content-Type: application/json
X-CSRFToken: <csrf_token>

Request body:

{
  "email": "admin@acme.com",
  "password": "changeme123"
}

Response (200):

{
  "status": 200,
  "data": {
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "admin@acme.com"
    },
    "method": {
      "method": "password",
      "at": 1706817600,
      "email": "admin@acme.com"
    }
  }
}

On success, the server sets a sessionid httpOnly cookie.

Warning

Login is rate-limited to 10 requests per minute per IP address.

Logout

DELETE /api/v1/auth/session

Headers:

X-CSRFToken: <csrf_token>

Response: 200 OK

Clears the session cookie.

Signup

POST /api/v1/auth/signup

Request body:

{
  "email": "newuser@acme.com",
  "password1": "securepassword123",
  "password2": "securepassword123"
}

Response: 200 OK with user data. If email verification is required (ACCOUNT_EMAIL_VERIFICATION = "mandatory"), the user must verify their email before logging in.

Re-authenticate

For sensitive operations that require recent authentication.

POST /api/v1/auth/reauthenticate

Request body:

{
  "password": "currentpassword123"
}

Password Management

Request Password Reset

POST /api/v1/auth/password/request

Request body:

{
  "email": "admin@acme.com"
}

Response: 200 OK (always returns 200, even if email is not found, to prevent enumeration).

Warning

Rate-limited to 5 requests per minute.

Validate Reset Key

GET /api/v1/auth/password/reset?key=<reset_key>

Response (200): Key is valid.

Response (400): Key is invalid or expired.

Reset Password

POST /api/v1/auth/password/reset

Request body:

{
  "key": "<reset_key>",
  "password": "newpassword123"
}

Change Password

Requires an authenticated session.

POST /api/v1/auth/password/change

Request body:

{
  "current_password": "oldpassword",
  "new_password": "newpassword123"
}

Email Verification

List Email Addresses

GET /api/v1/auth/account/email

Response:

{
  "status": 200,
  "data": [
    {
      "email": "admin@acme.com",
      "primary": true,
      "verified": true
    }
  ]
}

Add Email Address

POST /api/v1/auth/account/email

Request body:

{
  "email": "newemail@acme.com"
}

Verify Email

POST /api/v1/auth/account/email/verify

Request body:

{
  "key": "<verification_key>"
}

Mark Email as Primary

PATCH /api/v1/auth/account/email

Request body:

{
  "email": "newemail@acme.com",
  "primary": true
}

Login Code

For passwordless login flows.

Request Login Code

POST /api/v1/auth/code/request

Request body:

{
  "email": "admin@acme.com"
}

Confirm Login Code

POST /api/v1/auth/code/confirm

Request body:

{
  "code": "123456"
}

Auth Configuration

Get the available authentication methods and configuration.

GET /api/v1/auth/config

Response:

{
  "status": 200,
  "data": {
    "authentication_method": "email",
    "is_open_for_signup": true
  }
}

Current User

Get Current User

Returns the authenticated user with their tenant role and RBAC permissions.

GET /api/v1/users/me

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "admin@acme.com",
  "first_name": "Admin",
  "last_name": "User",
  "tenant_role": "admin",
  "permissions": [
    {"action": "list", "resource": "borrowers"},
    {"action": "show", "resource": "borrowers"},
    {"action": "create", "resource": "borrowers"},
    {"action": "edit", "resource": "borrowers"},
    {"action": "list", "resource": "loans"},
    {"action": "show", "resource": "loans"},
    {"action": "create", "resource": "loans"},
    {"action": "approve", "resource": "loans"},
    {"action": "*", "resource": "ledger_accounts"},
    {"type": "deny", "action": "show", "resource": "borrowers", "record": {"ssn": true}}
  ]
}

The permissions array follows the react-admin RBAC format:

  • {"action": "list", "resource": "borrowers"} --- allow listing borrowers
  • {"action": "*", "resource": "*"} --- superadmin wildcard
  • {"type": "deny", ...} --- deny a specific action (e.g., SSN field access)

Permission sources (merged):

  1. Tenant role mapping --- from common/rbac.py
  2. Django auth permissions --- from Group/Permission assignments
  3. Object-level grants --- from django-guardian

CSRF Token Flow

1. GET /api/v1/auth/session
   ← Set-Cookie: csrftoken=abc123

2. POST /api/v1/auth/login
   → Cookie: csrftoken=abc123
   → X-CSRFToken: abc123
   → {"email": "...", "password": "..."}
   ← Set-Cookie: sessionid=xyz789

3. POST /api/v1/loans
   → Cookie: sessionid=xyz789; csrftoken=abc123
   → X-CSRFToken: abc123
   → {"borrower_id": "...", ...}

See Also