Skip to main content

brixta-auth

Reusable authentication and organization-aware authorization for FastAPI.

Documented API: 0.1.2Python: >=3.11

brixta-auth is the backend security/authentication subsystem. It handles registration, password verification, organizations, memberships, roles, short-lived access JWTs, rotating refresh sessions, CSRF protection, logout, /me, and login rate limiting.

It does not render a login page. The frontend renders UI and calls this backend directly or through @brixtaorg/auth-client.

Architecture

Browser / frontend | | HTTP + cookies v FastAPI application | +-- brixta-auth | registration/login | Argon2 password verification | Ed25519 access JWT | refresh rotation | CSRF | roles | +-- brixta-core settings/database/middleware | v PostgreSQL / SQLite Valkey (optional shared rate limiting)

Install

Recommended:

python -m pip install
"brixta-core==0.1.2"
"brixta-auth[postgres,valkey]==0.1.2"

Without extras:

python -m pip install "brixta-auth==0.1.2"

Verify:

python - <<'PY' import brixta_core import brixta_auth print("brixta-core", brixta_core.version) print("brixta-auth", brixta_auth.version) PY

Public API

from brixta_auth import ( AuthService, AuthSettings, InMemoryFixedWindowRateLimiter, Membership, NoopRateLimiter, Organization, Principal, RedisFixedWindowRateLimiter, RefreshSession, Role, TokenService, User, ValkeyFixedWindowRateLimiter, build_principal_dependency, create_auth_router, require_roles, )

Important pieces:

API

Purpose

AuthSettings

Auth configuration

TokenService

Issues/verifies Ed25519 access JWTs

AuthService

Registration/login/refresh/logout business logic

create_auth_router()

Creates /auth/* FastAPI routes

build_principal_dependency()

Bearer JWT -> Principal

require_roles()

RBAC dependency

User

User ORM model

Organization

Organization/tenant ORM model

Membership

User-to-organization role membership

RefreshSession

Refresh-session ORM model

Role

owner, admin, engineer, viewer

InMemoryFixedWindowRateLimiter

Process-local limiter

ValkeyFixedWindowRateLimiter

Shared Valkey/Redis-compatible limiter

Generate Ed25519 keys

mkdir -p secrets

openssl genpkey
-algorithm Ed25519
-out secrets/auth_private.pem

openssl pkey
-in secrets/auth_private.pem
-pubout
-out secrets/auth_public.pem

Do not commit production private keys.

Configuration

AuthSettings reads BRIXTA_AUTH_* environment variables.

Important defaults:

BRIXTA_AUTH_ISSUER=brixta-foundation BRIXTA_AUTH_AUDIENCE=brixta-api BRIXTA_AUTH_PRIVATE_KEY_PATH=secrets/dev_private.pem BRIXTA_AUTH_PUBLIC_KEY_PATH=secrets/dev_public.pem BRIXTA_AUTH_ACCESS_TTL_SECONDS=600 BRIXTA_AUTH_REFRESH_TTL_DAYS=30 BRIXTA_AUTH_REFRESH_COOKIE_NAME=brixta_refresh BRIXTA_AUTH_CSRF_COOKIE_NAME=brixta_csrf BRIXTA_AUTH_COOKIE_SECURE=true BRIXTA_AUTH_COOKIE_SAMESITE=lax BRIXTA_AUTH_COOKIE_PATH=/api/v1/auth BRIXTA_AUTH_CSRF_COOKIE_PATH=/ BRIXTA_AUTH_PASSWORD_MIN_LENGTH=12 BRIXTA_AUTH_PASSWORD_MAX_LENGTH=128 BRIXTA_AUTH_LOGIN_LIMIT=10 BRIXTA_AUTH_LOGIN_WINDOW_SECONDS=60 BRIXTA_AUTH_RATE_LIMIT_BACKEND=memory

Local HTTP development often needs:

BRIXTA_DATABASE_URL=sqlite:///./app.db BRIXTA_AUTO_CREATE_TABLES=true

BRIXTA_AUTH_PRIVATE_KEY_PATH=secrets/auth_private.pem BRIXTA_AUTH_PUBLIC_KEY_PATH=secrets/auth_public.pem BRIXTA_AUTH_COOKIE_SECURE=false

Production example:

BRIXTA_DATABASE_URL=postgresql+psycopg://app:password@postgres:5432/app BRIXTA_CACHE_URL=redis://valkey:6379/0

BRIXTA_AUTH_PRIVATE_KEY_PATH=/run/secrets/auth_private.pem BRIXTA_AUTH_PUBLIC_KEY_PATH=/run/secrets/auth_public.pem BRIXTA_AUTH_COOKIE_SECURE=true BRIXTA_AUTH_COOKIE_SAMESITE=lax BRIXTA_AUTH_RATE_LIMIT_BACKEND=valkey

Critical cookie-path rule

The default refresh-cookie path is:

/api/v1/auth

If you mount the router somewhere else, change:

BRIXTA_AUTH_COOKIE_PATH=/your/real/auth/path

Otherwise refresh/logout cookies may not be sent to the correct endpoint.

Minimal backend integration

from collections.abc import Iterator from contextlib import asynccontextmanager from typing import Any

from fastapi import FastAPI from sqlalchemy.orm import Session

from brixta_core import ( Base, CoreSettings, RequestIDMiddleware, SecurityHeadersMiddleware, create_database, )

from brixta_auth import ( AuthService, AuthSettings, InMemoryFixedWindowRateLimiter, TokenService, ValkeyFixedWindowRateLimiter, create_auth_router, )

core_settings = CoreSettings() auth_settings = AuthSettings()

engine, SessionLocal = create_database(core_settings.database_url)

token_service = TokenService.from_settings(auth_settings) auth_service = AuthService( settings=auth_settings, token_service=token_service, )

def get_session() -> Iterator[Session]: with SessionLocal() as session: yield session

valkey_client: Any | None = None

if auth_settings.rate_limit_backend == "valkey": from redis import Redis valkey_client = Redis.from_url( core_settings.cache_url, decode_responses=True, ) limiter = ValkeyFixedWindowRateLimiter(valkey_client) else: limiter = InMemoryFixedWindowRateLimiter()

@asynccontextmanager async def lifespan(app: FastAPI): if core_settings.auto_create_tables: Base.metadata.create_all(engine)

if valkey_client is not None:
    valkey_client.ping()

yield

if valkey_client is not None:
    valkey_client.close()

engine.dispose()

app = FastAPI(lifespan=lifespan)

app.add_middleware(RequestIDMiddleware) app.add_middleware( SecurityHeadersMiddleware, hsts=core_settings.environment.casefold() == "production", )

app.include_router( create_auth_router( auth_service, auth_settings, session_dependency=get_session, rate_limiter=limiter, ), prefix="/api/v1", )

This creates:

POST /api/v1/auth/register POST /api/v1/auth/login POST /api/v1/auth/refresh POST /api/v1/auth/logout GET /api/v1/auth/me

Database models

brixta-auth defines:

User Organization Membership RefreshSession

User includes:

id email full_name password_hash is_active is_verified token_version created_at updated_at

Membership links a user to an organization and carries one role.

Roles in 0.1.2:

owner admin engineer viewer

RefreshSession stores a hash of the refresh token, not the plaintext refresh token.

Register

POST /api/v1/auth/register Content-Type: application/json

{ "email": "owner@example.com", "password": "correct-horse-battery-staple", "full_name": "Plant Owner", "organization_name": "Example Cement Plant" }

Registration creates:

User + Organization + Membership(role="owner")

Registration does not automatically log the user in.

Login

POST /api/v1/auth/login Content-Type: application/json

{ "email": "owner@example.com", "password": "correct-horse-battery-staple" }

Optional organization:

{ "email": "owner@example.com", "password": "correct-horse-battery-staple", "organization_id": "79a7789b-46ef-4e21-b257-9edcaed03f60" }

Response:

{ "access_token": "", "token_type": "bearer", "expires_in": 600 }

The backend also sets:

brixta_refresh HttpOnly refresh-token cookie brixta_csrf readable CSRF cookie

Access tokens

Access JWT claims include:

iss aud sub sid jti iat nbf exp typ ver roles org

They are signed with EdDSA/Ed25519.

Default lifetime:

600 seconds

Verification is normally stateless/local. A disabled account can therefore retain access until the current short access token expires unless the consuming application adds a stricter database-backed check for a high-risk endpoint.

Refresh

POST /api/v1/auth/refresh X-CSRF-Token: <brixta_csrf-cookie-value>

Browser cookies are also sent.

The backend:

checks CSRF hashes + locates refresh token validates session validates active user/membership rotates refresh session revokes previous token issues new access token sets new refresh + CSRF cookies

Reuse of an already-rotated refresh token revokes the token family.

Logout

POST /api/v1/auth/logout X-CSRF-Token: <brixta_csrf-cookie-value>

The backend revokes the refresh session and clears auth cookies.

Current principal

GET /api/v1/auth/me Authorization: Bearer

Response:

{ "user_id": "5f7e5b6a-...", "session_id": "b7f07817-...", "organization_id": "79a7789b-...", "roles": ["owner"] }

/me is the authenticated principal represented by token claims, not a complete editable user profile.

Protect custom FastAPI routes

from typing import Annotated

from fastapi import Depends from brixta_auth import Principal, build_principal_dependency

principal_required = build_principal_dependency(token_service)

@app.get("/api/v1/plants") def list_plants( principal: Annotated[Principal, Depends(principal_required)], ): return { "organization_id": str(principal.organization_id), "roles": principal.roles, }

Role-protected routes

from typing import Annotated

from fastapi import Depends from brixta_auth import Principal, require_roles

engineer_or_admin = require_roles( principal_required, "engineer", "admin", )

@app.post("/api/v1/kilns/{kiln_id}/setpoint") def setpoint( kiln_id: str, principal: Annotated[Principal, Depends(engineer_or_admin)], ): return { "kiln_id": kiln_id, "changed_by": str(principal.user_id), }

No allowed role -> HTTP 403.

Rate limiting

Development:

from brixta_auth import InMemoryFixedWindowRateLimiter limiter = InMemoryFixedWindowRateLimiter()

Production/shared:

from redis import Redis from brixta_auth import ValkeyFixedWindowRateLimiter

client = Redis.from_url( "redis://localhost:6379/0", decode_responses=True, )

limiter = ValkeyFixedWindowRateLimiter(client)

Browser integration

Recommended:

npm install "@brixtaorg/auth-client@0.1.2"

import { BrixtaAuthClient } from "@brixtaorg/auth-client";

const auth = new BrixtaAuthClient({ baseUrl: "/api/v1", });

await auth.login({ email, password }); const me = await auth.me();

The NPM client keeps the short-lived access token in browser memory.

The Python backend remains responsible for:

password verification JWT signing JWT verification refresh-session persistence refresh rotation CSRF enforcement role claims / auth endpoint behavior

The refresh token is a server-issued HttpOnly cookie and is not readable by JavaScript.

Cross-origin frontend/API

The NPM client uses:

credentials: include

For a cross-origin frontend, configure credentialed CORS in the consuming FastAPI app:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware( CORSMiddleware, allow_origins=["https://app.example.com"], allow_credentials=True, allow_methods=[""], allow_headers=[""], )

Also configure cookie Secure, SameSite, domain and path correctly for your deployment.

Production checklist

use proper DB migrations

keep Ed25519 private key backend-only

HTTPS

BRIXTA_AUTH_COOKIE_SECURE=true

explicit CORS origins for credentialed browser apps

correct cookie path/domain

Valkey/shared limiter for multi-process/multi-instance production

do not persist browser access tokens in localStorage

do not expose plaintext refresh tokens

test role/tenant authorization boundaries

Not provided in 0.1.2

Do not assume these exist:

password reset / forgot password complete email verification workflow MFA/TOTP OAuth/social login SAML/SSO organization invitations organization-admin REST endpoints profile-editing endpoints frontend UI components

There is an is_verified model field, but no complete email-verification delivery workflow in 0.1.2.

LLM IMPLEMENTATION CONTRACT

Copy this into an LLM prompt:

Use the installed brixta-auth package. Do not recreate its authentication internals.

Packages: brixta-core brixta-auth

Prefer these public APIs: AuthSettings AuthService TokenService create_auth_router build_principal_dependency require_roles Principal Role User Organization Membership RefreshSession InMemoryFixedWindowRateLimiter ValkeyFixedWindowRateLimiter

Rules:

  1. Create DB engine/session via brixta_core.create_database().
  2. Create AuthSettings.
  3. Create TokenService.from_settings(auth_settings).
  4. Create AuthService(settings=auth_settings, token_service=token_service).
  5. Supply a SQLAlchemy Session dependency.
  6. Mount create_auth_router under /api/v1 unless instructed otherwise.
  7. If the auth route prefix changes, make BRIXTA_AUTH_COOKIE_PATH match the real auth path.
  8. Use build_principal_dependency(token_service) for authenticated domain endpoints.
  9. Use require_roles(...) for RBAC.
  10. Do not create another password/JWT/refresh/CSRF implementation.
  11. Keep the Ed25519 private key backend-only.
  12. Do not store plaintext refresh tokens in the database.
  13. In production use migrations, HTTPS, Secure cookies and explicit CORS.
  14. In multi-instance production prefer Valkey rate limiting.
  15. Do not invent password reset, MFA, OAuth, invitation or admin APIs that are not implemented.
  16. For browser apps prefer @brixtaorg/auth-client.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

brixta_auth-0.1.3.tar.gz (20.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

brixta_auth-0.1.3-py3-none-any.whl (18.4 kB view details)

Uploaded Python 3

File details

Details for the file brixta_auth-0.1.3.tar.gz.

File metadata

  • Download URL: brixta_auth-0.1.3.tar.gz
  • Upload date:
  • Size: 20.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for brixta_auth-0.1.3.tar.gz
Algorithm Hash digest
SHA256 085186f579dd216f052676fbdfe3a1b7edc9775eb32822cde0fe7eb5c31608fd
MD5 5057b56c51dbed69cdc3e7edcad31880
BLAKE2b-256 c92f612f4a83c66414c5d02196188fe2364d63392693ffd4e281840cc5de9362

See more details on using hashes here.

Provenance

The following attestation bundles were made for brixta_auth-0.1.3.tar.gz:

Publisher: release.yml on habibieebhy/brixtafoundation

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file brixta_auth-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: brixta_auth-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 18.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for brixta_auth-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 7b5c0282261199b15038beb09d47b6c23bb8b9fd685538ff418f7b6b33f73b08
MD5 c5b05376743b0b0b0929c37af539acdb
BLAKE2b-256 7790f7e507e1b31a298a9ae6a9be303a73ddb2b885952789a366640a335910dd

See more details on using hashes here.

Provenance

The following attestation bundles were made for brixta_auth-0.1.3-py3-none-any.whl:

Publisher: release.yml on habibieebhy/brixtafoundation

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page