# Architecture (/docs/architecture)
Layers [#layers]
| Layer | Role | Code |
| ----------------- | --------------------------------------------------------------------------------------------- | ------------------------------------ |
| **Transport** | Reads/writes the **session token** on HTTP (Bearer header or cookie) | `aura_auth.transport` |
| **Strategy** | Validates credentials and returns a **user** (e.g. password on a credential account) | `aura_auth.strategies.password` |
| **Backend** | Implements **`BackendProtocol`** — CRUD for users, accounts, sessions, verifications | `aura_auth.backend.sqlalchemy` |
| **Orchestration** | **`AuraAuth`**, **`UserManager`** — register, login, sessions, verification helpers | `aura_auth.app`, `aura_auth.manager` |
Contracts (protocols, schemas, config, exceptions, security helpers) live in **`aura_auth._core`**. Higher layers depend on protocols, not on each other’s concrete types.
Four tables [#four-tables]
1. **User** — identity: `name`, `email`, `email_verified`, `image`, timestamps.
2. **Account** — one row per sign-in method: `provider_id` (e.g. `"credential"`), `account_id`, optional OAuth fields, **`password`** (hashed) for credential accounts.
3. **Session** — opaque **`token`**, **`user_id`**, **`expires_at`**, optional IP / user agent.
4. **Verification** — short-lived rows for email verification, resets, etc.
Login creates a **session** row; **`current_user`** resolves the token against the session table, then loads the user by id. This is **not** a stateless JWT login flow (though **`TokenHelper`** remains available for other signing needs).
Request flow (login) [#request-flow-login]
1. Client posts email/password to **`POST /auth/login`**.
2. **Password strategy** finds the credential **account**, verifies bcrypt, returns the **user**.
3. **UserManager** creates a **session** with a new opaque token.
4. Response returns **`AuthResponse`**; transport may set **Bearer** in the body only, or also **`Set-Cookie`** when cookie transport is enabled.
# Configuration (/docs/configuration)
`AuraAuth(database_url=..., secret=..., **kwargs)` merges supported keys onto **`AuraAuthConfig`** (`aura_auth._core.config`).
Routing [#routing]
| Field | Default | Purpose |
| ------------------ | --------- | ------------------------------------------------------------- |
| **`route_prefix`** | `"/auth"` | Prefix for routes returned by **`auth.router`**. |
| — | — | Use **`auth.raw_router`** to mount with your own prefix. |
Sessions [#sessions]
| Field | Default | Purpose |
| ----------------------------------- | ----------------- | ----------------------------------------------------------------- |
| **`session_lifetime_seconds`** | `604800` (7 days) | Session row expiry; cookie **`max-age`** when using cookies. |
| **`verify_token_lifetime_seconds`** | `86400` | Verification rows (e.g. email verify). |
Cookies [#cookies]
When **`cookie_transport`** is `True`, register/login call **`set_token`** on the response (HTTP-only cookie by default).
| Field | Default | Purpose |
| ---------------------- | -------------- | ---------------------------------------------------------------------------------- |
| **`cookie_transport`** | `False` | Use cookie instead of Bearer for **`get_token`** / **`set_token`**. |
| **`cookie_name`** | `"aura_token"` | Cookie name. |
| **`cookie_secure`** | `True` | **`Secure`** flag. Use **`False`** for `http://` local dev. |
| **`cookie_httponly`** | `True` | HTTP-only. |
| **`cookie_samesite`** | `"lax"` | `lax`, `strict`, or `none`. |
With **`cookie_transport=True`**, the client must send the cookie; the **`Authorization`** header is **not** read by the default transport.
Passwords [#passwords]
| Field | Default |
| ------------------------- | ------- |
| **`password_min_length`** | `8` |
Models (SQLAlchemy) [#models-sqlalchemy]
Optional overrides passed to the backend:
* **`user_model`**
* **`account_model`**
* **`session_model`**
* **`verification_model`**
If omitted, the built-in **`Default*`** models from **`aura_auth.models.sqlalchemy`** are used.
Advanced [#advanced]
| Field | Purpose |
| ------------ | ------------------------------------------------------------- |
| **`engine`** | Inject a pre-built async SQLAlchemy engine (common in tests). |
The **`secret`** field is required on the public constructor today (reserved for signing and future JWT-oriented features).
# Contributing (/docs/contributing)
Clone and Python env [#clone-and-python-env]
From the repository root (parent of this `docs/` app):
```bash
uv sync
uv run ruff check .
uv run ruff format .
uv run ty check
uv run pytest
```
Or use **`make check`** if the Makefile is present.
Agent / contributor notes [#agent--contributor-notes]
See **`AGENTS.md`** at the repo root for the phased development plan, import boundaries (`_core` stays free of router/backend imports), docstring style, and test layout.
Some phase descriptions predate the **account + session** architecture; treat the **source tree** and **`README.md`** as canonical for current behavior.
Documentation site (this folder) [#documentation-site-this-folder]
```bash
cd docs
pnpm install
pnpm dev
```
Build:
```bash
pnpm build
```
Set **`OPENROUTER_API_KEY`** (or your chosen provider) if you use the bundled **Ask AI** panel — the model is not provided by Fumadocs.
# For LLMs and agents (/docs/for-llms-and-agents)
This documentation site is built with **Fumadocs**. The following endpoints help **LLMs**, **IDE agents**, and **automation** ingest the same content as the HTML docs.
Endpoints [#endpoints]
| URL | Description |
| -------------------------- | --------------------------------------------------------------------- |
| **`/llms.txt`** | Compact **index** of pages (titles and URLs), suitable for discovery. |
| **`/llms-full.txt`** | **Full** processed markdown for **all** pages, concatenated. |
| **`/docs/...path....mdx`** | Single page as markdown (rewritten to the internal MDX route). |
Use **`Accept`** headers that prefer markdown where supported (see [Fumadocs negotiation](https://fumadocs.dev/docs/ui/llms)); this project’s **`proxy`** can rewrite doc paths for markdown-friendly clients.
On each docs page [#on-each-docs-page]
Use **“Copy Markdown”** and **view options** in the page header to copy or open the **`.mdx`** URL for that page.
Source of truth [#source-of-truth]
Library behavior is defined by the **Python package** in this repository (`src/aura_auth`) and tests under **`tests/`**. If this site and **`README.md`** disagree with **`AGENTS.md`** phase text, prefer the **implementation** and the **README**.
Repository: [github.com/snapwre/aura-auth](https://github.com/snapwre/aura-auth)
# Introduction (/docs)
**Aura Auth** is a small, explicit library for **FastAPI** and **async SQLAlchemy**. The default install gives you **email/password** registration and login with:
* **Identity vs credentials** — a `User` row for who someone is, and `Account` rows for how they sign in (including `provider_id="credential"` with a bcrypt hash).
* **Server-side sessions** — opaque tokens stored in the database so you can **revoke** them, list devices, and log out cleanly.
* **Optional cookie transport** — HTTP-only session cookies, or **Bearer** tokens in the `Authorization` header.
The project is **early beta**: pin versions in production and follow release notes when upgrading.
Install [#install]
Requires **Python 3.11+**.
```bash
uv add aura-auth
# or: pip install aura-auth
```
Install from GitHub during beta:
```bash
uv add "aura-auth @ git+https://github.com/snapwre/aura-auth.git"
```
Next steps [#next-steps]
# Models and backend (/docs/models-and-backend)
Mixins [#mixins]
Under **`aura_auth.models.base`**, mixins define the columns shared by the default implementation:
* **`UserMixin`** — identity fields (no password on the user row).
* **`AccountMixin`** — links to `user_id`, `provider_id`, `account_id`, tokens, **`password`** for credential provider, timestamps.
* **`SessionMixin`** — `token`, `expires_at`, optional IP / user agent.
* **`VerificationMixin`** — `identifier`, `value`, `expires_at`, timestamps.
Subclass a mixin with your declarative **`Base`**, add columns, then pass **`user_model=...`** (and optionally account/session/verification models) into **`AuraAuth`**.
Default models [#default-models]
**`aura_auth.models.sqlalchemy`** exposes **`DefaultUser`**, **`DefaultAccount`**, **`DefaultSession`**, **`DefaultVerification`** and a shared **`Base`** for **`create_tables`**.
BackendProtocol [#backendprotocol]
The SQLAlchemy backend implements async methods for:
* **Users** — `get_user_by_id`, `get_user_by_email`, `create_user`, `update_user`, `delete_user`
* **Accounts** — `get_account`, `get_accounts_by_user`, `create_account`, `update_account`, `delete_account`
* **Sessions** — `create_session`, `get_session_by_token`, `list_sessions_by_user`, deletes by id/token/user
* **Verifications** — `create_verification`, `get_verification`, `delete_verification`, `delete_expired_verifications`
Strategies and **`UserManager`** depend on this protocol so another database layer could be swapped in later.
# Quickstart (/docs/quickstart)
Minimal app [#minimal-app]
```python
from fastapi import Depends, FastAPI
from aura_auth import AuraAuth
app = FastAPI()
auth = AuraAuth(
database_url="sqlite+aiosqlite:///./app.db",
secret="change-me-to-a-long-random-secret-at-least-32-chars",
)
@app.on_event("startup")
async def startup() -> None:
await auth.create_tables()
auth.init_app(app)
@app.get("/protected")
async def protected(user=Depends(auth.current_user())):
return {"message": f"Hello, {user.name}"}
```
`init_app` registers the bundled router (by default under **`/auth`**) and maps library exceptions to HTTP responses.
Register and login [#register-and-login]
| Method | Path | Body / notes |
| ------ | ---------------- | ---------------------------------------------------- |
| `POST` | `/auth/register` | `{"name": "...", "email": "...", "password": "..."}` |
| `POST` | `/auth/login` | `{"email": "...", "password": "..."}` |
Both return **`AuthResponse`**: a **`user`** object and a **`session`** with **`token`**, **`expires_at`**, and **`id`**.
Authenticated requests [#authenticated-requests]
**Bearer (default)** — send the session token from the JSON body:
```http
Authorization: Bearer
```
**Cookie** — set `cookie_transport=True` (and usually `cookie_secure=False` on plain HTTP during local dev). Register/login set **`Set-Cookie`**; `Depends(auth.current_user())` reads the same cookie. See [Sessions and cookies](/docs/sessions-and-cookies).
Dependencies [#dependencies]
* **`Depends(auth.current_user())`** — 401 if not logged in.
* **`Depends(auth.current_verified_user())`** — same, plus 403 if `email_verified` is false.
Custom route prefix [#custom-route-prefix]
```python
auth = AuraAuth(
database_url="sqlite+aiosqlite:///./app.db",
secret="...",
route_prefix="/api/v1/auth",
)
```
To mount paths yourself, use **`auth.raw_router`** with your own `prefix`.
# Sessions and cookies (/docs/sessions-and-cookies)
Session token [#session-token]
After **register** or **login**, the client receives **`session.token`** — an **opaque** string stored in the **`sessions`** table. The API checks it with **`get_session_by_token`**; expired or deleted sessions yield **401**.
Benefits over stateless JWT-only login:
* **Revocation** — delete the row (logout, compromised token).
* **Device lists** — **`GET /auth/sessions`** (non-expired sessions for the current user).
* **Per-session logout** — **`DELETE /auth/sessions/{session_id}`**.
Bearer transport (default) [#bearer-transport-default]
`cookie_transport=False` (**default**)
* Clients send **`Authorization: Bearer `**.
* **`set_token`** on the response is a no-op for Bearer (token is only in JSON).
Cookie transport [#cookie-transport]
`cookie_transport=True`
* **Register** / **login** responses include **`Set-Cookie`** (name from **`cookie_name`**, default `aura_token`).
* **`Depends(auth.current_user())`** reads the token via **`request.cookies`**.
Local HTTP [#local-http]
Browsers **do not** send cookies with the **`Secure`** attribute over plain **`http://`**. For local development set:
```python
AuraAuth(
database_url="...",
secret="...",
cookie_transport=True,
cookie_secure=False,
)
```
Use **`cookie_secure=True`** (default) behind **HTTPS** in production.
Logout [#logout]
**`POST /auth/logout`** revokes the current session (by token from header or cookie) and clears the cookie when cookie transport is enabled.