Skip to content

Authentication

AuthFort provides authentication through both REST endpoints (via the FastAPI router) and a programmatic API (for use in backend code).

Creates a new user with email and password.

Endpoint
POST /auth/signup
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "strongpassword",
  "name": "Jane Doe",
  "avatar_url": "https://example.com/avatar.jpg",
  "phone": "+1234567890"
}
POST /auth/signup
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "strongpassword",
  "name": "Jane Doe",
  "avatar_url": "https://example.com/avatar.jpg",
  "phone": "+1234567890"
}

Returns AuthResponse with user info and tokens. Returns 400 if the email is already taken, or 403 if allow_signup=False.

Programmatic
result = await auth.create_user(
    "user@example.com",
    "strongpassword",
    name="Jane Doe",
    avatar_url="https://example.com/avatar.jpg",
    phone="+1234567890",
)
# result.user.id, result.user.email, result.tokens.access_token

# Admin-created accounts can skip email verification
result = await auth.create_user(
    "admin@example.com",
    "strongpassword",
    name="Admin User",
    email_verified=True,  # fires email_verified event
)
result = await auth.create_user(
    "user@example.com",
    "strongpassword",
    name="Jane Doe",
    avatar_url="https://example.com/avatar.jpg",
    phone="+1234567890",
)
# result.user.id, result.user.email, result.tokens.access_token

# Admin-created accounts can skip email verification
result = await auth.create_user(
    "admin@example.com",
    "strongpassword",
    name="Admin User",
    email_verified=True,  # fires email_verified event
)

create_user() always works regardless of the allow_signup setting. Use it for admin-created accounts or seeding.

Passwords are hashed with argon2 before storage. Emails are normalized (stripped and lowercased).

All fields except email and password are optional: name, avatar_url, and phone.

Update a user’s profile fields. Uses a sentinel pattern so that omitted fields are left unchanged, while passing None explicitly clears a field:

Programmatic
# Update specific fields — only provided fields are changed
updated = await auth.update_user(
    user_id,
    name="New Name",
    phone="+1987654321",
)

# Pass None to clear a field
updated = await auth.update_user(user_id, phone=None)

# Omitted fields are not touched — this only changes the name
updated = await auth.update_user(user_id, name="Jane")

# Admin can manually verify a user's email
updated = await auth.update_user(user_id, email_verified=True)
# Update specific fields — only provided fields are changed
updated = await auth.update_user(
    user_id,
    name="New Name",
    phone="+1987654321",
)

# Pass None to clear a field
updated = await auth.update_user(user_id, phone=None)

# Omitted fields are not touched — this only changes the name
updated = await auth.update_user(user_id, name="Jane")

# Admin can manually verify a user's email
updated = await auth.update_user(user_id, email_verified=True)

Returns: UserResponse with the updated user data. Emits a user_updated event with the list of changed fields.

Authenticates a user and issues tokens.

Endpoint
POST /auth/login
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "strongpassword"
}
POST /auth/login
Content-Type: application/json

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

Returns AuthResponse with tokens. Returns 401 if credentials are invalid. If the user is banned, returns 403.

Programmatic
result = await auth.login("user@example.com", "strongpassword")
# result.tokens.access_token, result.tokens.refresh_token
result = await auth.login("user@example.com", "strongpassword")
# result.tokens.access_token, result.tokens.refresh_token

On successful login, a login event is emitted. On failure, a login_failed event is emitted with the reason.

Exchanges a valid refresh token for a new access token and refresh token pair.

Endpoint
POST /auth/refresh
Content-Type: application/json

{
  "refresh_token": "the-refresh-token"
}
POST /auth/refresh
Content-Type: application/json

{
  "refresh_token": "the-refresh-token"
}

In cookie mode, the refresh token is read from the cookie automatically — the body can be empty.

Programmatic
result = await auth.refresh("the-refresh-token")
result = await auth.refresh("the-refresh-token")

Refresh tokens are single-use. The old token is revoked and a new pair is issued. If a revoked token is reused (indicating theft), the entire token family is revoked.

Revokes a refresh token, ending the session.

Endpoint
POST /auth/logout
Content-Type: application/json

{
  "refresh_token": "the-refresh-token"
}
POST /auth/logout
Content-Type: application/json

{
  "refresh_token": "the-refresh-token"
}

In cookie mode, the refresh token is read from the cookie automatically — the body can be empty.

Programmatic
await auth.logout("the-refresh-token")
await auth.logout("the-refresh-token")

The /auth/me endpoint returns the authenticated user’s info:

GET /auth/me
Authorization: Bearer <access-token>
GET /auth/me
Authorization: Bearer <access-token>

In cookie mode, the access token is read from the cookie automatically — no Authorization header needed.

Returns a UserResponse with: id, email, name, email_verified, avatar_url, phone, banned, roles, created_at, and session_id.

AuthFort also supports passwordless authentication via magic links and email OTP. These methods don’t require a password — users authenticate by clicking a link or entering a code sent to their email.

Both methods automatically verify the user’s email address.