Metadata-Version: 2.4
Name: better-auth-server
Version: 1.1.0
Summary: Authentication for Python, ported from better-auth, with FastAPI, Litestar, Flask and Django integrations.
Keywords: auth,authentication,oauth,oauth2,oidc,session,fastapi,litestar,flask,django,better-auth
Author: Oumar Barry
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP :: Session
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Dist: cryptography>=49.0.0
Requires-Dist: httpx>=0.27
Requires-Dist: pycryptodome>=3.23.0
Requires-Dist: pyjwt>=2.13.0
Requires-Dist: pynacl>=1.6.2
Requires-Dist: django>=5.0 ; extra == 'django'
Requires-Dist: fastapi>=0.110 ; extra == 'fastapi'
Requires-Dist: flask>=3.0 ; extra == 'flask'
Requires-Dist: litestar>=2.24 ; extra == 'litestar'
Requires-Dist: webauthn>=2.7 ; extra == 'passkey'
Requires-Dist: sqlalchemy[asyncio]>=2.0 ; extra == 'sqlalchemy'
Requires-Dist: dnspython>=2.7 ; extra == 'sso'
Requires-Python: >=3.10
Project-URL: Homepage, https://github.com/oumarbarry/better-auth-py
Project-URL: Documentation, https://better-auth-py.oumarbarry.dev
Project-URL: Repository, https://github.com/oumarbarry/better-auth-py
Project-URL: Issues, https://github.com/oumarbarry/better-auth-py/issues
Project-URL: Changelog, https://github.com/oumarbarry/better-auth-py/blob/main/CHANGELOG.md
Provides-Extra: django
Provides-Extra: fastapi
Provides-Extra: flask
Provides-Extra: litestar
Provides-Extra: passkey
Provides-Extra: sqlalchemy
Provides-Extra: sso
Description-Content-Type: text/markdown

# Better Auth for Python

[![CI](https://github.com/oumarbarry/better-auth-py/actions/workflows/ci.yml/badge.svg)](https://github.com/oumarbarry/better-auth-py/actions/workflows/ci.yml)

**Authentication for Python, ported from [better-auth](https://better-auth.com). Ships with FastAPI, Litestar, Flask and Django integrations, plus a Python client.**

This is `better-auth-server`, the server package (import name `better_auth`). Docs: **[better-auth-py.oumarbarry.dev](https://better-auth-py.oumarbarry.dev)**

Your users, sessions and accounts live in your own database. There is no hosted service to depend on and no per-user pricing. The API surface is the one the TypeScript original has proven in production.

Full parity with better-auth (TypeScript) **v1.7.6**: same routes, JSON shapes and error codes, 36 social providers, 27 built-in plugins.

```python
from better_auth import BetterAuth, EmailAndPassword
from better_auth.integrations.fastapi import BetterAuthFastAPI
from fastapi import Depends, FastAPI

auth = BetterAuth(
    secret="...",  # openssl rand -base64 32
    base_url="http://localhost:8000",
    email_and_password=EmailAndPassword(enabled=True),
)

app = FastAPI()
ba = BetterAuthFastAPI(auth)
app.include_router(ba.router)  # mounts /api/auth/*

@app.get("/me")
async def me(result: dict = Depends(ba.require_session)):
    return result["user"]
```

These twenty lines are a working auth server. Sign-up, sign-in, sessions, sign-out, password reset, email verification and social login are mounted under `/api/auth`, with the same routes, JSON shapes and error codes as better-auth.

## Features

- Email and password: sign-up, sign-in, change/set/verify password, reset flow, email verification.
- Social sign-in (OAuth2/OIDC): 36 built-in providers (GitHub, Google, Discord, Apple, GitLab, Microsoft Entra ID, Slack, Spotify, Twitch, Zoom, ...), custom providers in a few lines. PKCE, single-use database-backed state, token refresh, JWKS/id-token verification, and account linking guarded by provider email verification.
- Sessions in your database: HMAC-signed cookies, sliding expiry (`expires_in`/`update_age`), `rememberMe`, list and revoke endpoints, bearer tokens for API clients, an optional signed cookie cache to skip the DB read on `/get-session`.
- Two adapters out of the box: in-memory for dev and tests, SQLAlchemy 2 async for SQLite, PostgreSQL and MySQL (SQLModel engines work as-is). A custom adapter implements nine async CRUD methods over dict rows.
- 27 built-in plugins covering two-factor auth, admin, organization (teams + dynamic access control), API keys, passkeys (WebAuthn), JWT, an OAuth 2.1 authorization-server (`oauth-provider`), SSO (OIDC), generic OAuth, device authorization, SIWE (Sign-In with Ethereum), magic link, email OTP, username, anonymous sessions, multi-session and more. Plugins add routes, extend the database schema, and hook before/after every request.
- Pluggable secondary storage (Redis-shaped protocol), configurable rate limiting with better-auth's per-path rules, trusted-proxy client-IP resolution, and secrets rotation via versioned `SecretConfig`.
- Secure defaults: scrypt password hashing, CSRF origin checks, open-redirect protection on every `callbackURL`, timing-equalized sign-in, XChaCha20-Poly1305 cross-runtime encryption for stored secrets.
- The core is framework-agnostic. The FastAPI layer is about 80 lines over plain request/response dataclasses, and the Litestar (`BetterAuthLitestar`), Flask (`BetterAuthFlask`) and Django (`BetterAuthDjango`) layers follow the same pattern, WSGI included.

## Compatibility with better-auth (TypeScript)

The wire protocol and storage format follow the TypeScript implementation closely. A Python service can share a database with a TypeScript better-auth app:

| | |
|---|---|
| Routes and JSON shapes | Same paths (`/sign-in/email`, `/get-session`, `/callback/{provider}`, ...), same success and error bodies, same codes (`USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL` 422, `INVALID_EMAIL_OR_PASSWORD` 401, ...) |
| Database schema | Identical `user` / `session` / `account` / `verification` tables, camelCase columns |
| Password hashes | Exact scrypt format (`N=16384, r=16, p=1, dkLen=64`, NFKC, hex `salt:key`). Passwords created by the TypeScript library verify in Python, and vice versa. |
| Session cookies | Same name (`better-auth.session_token`, `__Secure-` over HTTPS) and signing scheme (HMAC-SHA256, base64, URI-encoded `token.sig`) |
| IDs and tokens | Same alphabets and lengths (62-character IDs, 64-character state and verification tokens) |

Known divergences: reset-password tokens are stored in the database (email-verification tokens are stateless HS256 JWTs, matching the TypeScript library); bearer-token reading is built into the core session layer (a plugin over there, `bearer` here only adds the response-side `set-auth-token` header); SAML (part of the `sso` plugin), `scim`, `stripe` and the browser client/expo/electron/cli packages are out of scope (server-side parity only, see the changelog; to call a Better Auth server *from* Python there is [`better-auth-client`](https://pypi.org/project/better-auth-client/)).

## Install

```bash
uv add better-auth-server[fastapi,sqlalchemy]
# or: pip install "better-auth-server[fastapi,sqlalchemy]"
```

Extras: `fastapi` (the FastAPI integration), `litestar` (the Litestar integration), `flask` (the Flask integration), `django` (the Django integration), `sqlalchemy` (the async SQLAlchemy adapter), `passkey` (WebAuthn via `webauthn`, for the `passkey` plugin), `sso` (DNS TXT lookups via `dnspython`, for the `sso` plugin's domain verification). Requires Python 3.10 to 3.14.

## Quickstart

Run the included demo:

```bash
uv run uvicorn examples.fastapi_app:app --reload
```

```bash
# health
curl -s localhost:8000/api/auth/ok

# sign up (sets a session cookie)
curl -s -c /tmp/jar -X POST localhost:8000/api/auth/sign-up/email \
  -H 'content-type: application/json' \
  -d '{"name": "Ada", "email": "ada@example.com", "password": "s3cret-password"}'

# who am I?
curl -s -b /tmp/jar localhost:8000/api/auth/get-session
curl -s -b /tmp/jar localhost:8000/me

# sign out
curl -s -b /tmp/jar -c /tmp/jar -X POST localhost:8000/api/auth/sign-out
```

API clients can skip cookies entirely and send `Authorization: Bearer <token>` with the `token` returned by sign-in or sign-up.

## Configuration

```python
from better_auth import (
    BetterAuth, EmailAndPassword, EmailVerification, SessionOptions, RateLimit, GitHub, Google,
)

async def send_reset(user, url, token): ...       # plug your mailer
async def send_verification(user, url, token): ...

auth = BetterAuth(
    secret=os.environ["BETTER_AUTH_SECRET"],       # >= 32 chars, required
    base_url="https://example.com",                # cookies become Secure/__Secure- on https
    base_path="/api/auth",                         # default
    adapter=SQLAlchemyAdapter(engine),             # default: MemoryAdapter() (dev only!)
    email_and_password=EmailAndPassword(
        enabled=True,
        min_password_length=8,
        require_email_verification=False,
        auto_sign_in=True,
        send_reset_password=send_reset,
        revoke_sessions_on_password_reset=False,
    ),
    email_verification=EmailVerification(
        send_verification_email=send_verification,
        send_on_sign_up=False,
        auto_sign_in_after_verification=False,
    ),
    social_providers={
        "github": GitHub(client_id="...", client_secret="..."),
        "google": Google(client_id="...", client_secret="..."),
    },
    session=SessionOptions(expires_in=7 * 86400, update_age=86400),
    rate_limit=RateLimit(enabled=True),            # better-auth path rules built in
    trusted_origins=["https://app.example.com"],   # extra origins for CSRF + redirects
    plugins=[...],
    hooks={"before": ..., "after": ...},           # around every auth request
    database_hooks={"user": {"create": {"before": ..., "after": ...}}},
)
```

## Database

Tables follow better-auth's core schema (`user`, `session`, `account`, `verification`).

```python
from sqlalchemy.ext.asyncio import create_async_engine
from better_auth.adapters.sqlalchemy import SQLAlchemyAdapter

engine = create_async_engine("postgresql+asyncpg://...")  # or sqlite+aiosqlite, mysql+aiomysql
adapter = SQLAlchemyAdapter(engine)
auth = BetterAuth(secret=..., adapter=adapter, ...)
await adapter.create_tables()  # dev convenience; use Alembic in production
```

A custom adapter implements nine async methods over dict rows. See `better_auth.adapters.base.BaseAdapter` (`create`, `find_one`, `find_many`, `update`, `update_many`, `delete`, `delete_many`, `count`, `transaction`); atomic `consume_one`/`increment_one` come for free as guarded fallbacks built on those methods, and an adapter can override them with a native single statement.

## Social providers

36 providers are built in: GitHub, Google, Discord, Apple, Atlassian, AWS Cognito, Cloudflare, Dropbox, Facebook, Figma, GitLab, Hugging Face, Kakao, Kick, LINE, Linear, LinkedIn, Microsoft Entra ID, Naver, Notion, Paybin, PayPal, Polar, Railway, Reddit, Roblox, Salesforce, Slack, Spotify, TikTok, Twitch, Twitter/X, Vercel, VK, WeChat and Zoom (see `better_auth.oauth.PROVIDER_REGISTRY` for the full name-to-class map). Configure by instance or by name:

```python
from better_auth import GitHub

social_providers = {
    "github": GitHub(client_id=..., client_secret=...),
    # or name-keyed, resolved against PROVIDER_REGISTRY:
    "gitlab": {"client_id": ..., "client_secret": ...},
}
```

`POST /api/auth/sign-in/social {"provider": "github", "callbackURL": "/dashboard"}` returns `{"url": ..., "redirect": true}`. Send the browser to that URL; the callback sets the session cookie and redirects to `callbackURL`. A custom provider is one dataclass:

```python
from better_auth import OAuthProvider

okta = OAuthProvider(
    client_id=..., client_secret=..., provider_id="okta",
    authorization_endpoint="https://your-org.okta.com/oauth2/v1/authorize",
    token_endpoint="https://your-org.okta.com/oauth2/v1/token",
    userinfo_endpoint="https://your-org.okta.com/oauth2/v1/userinfo",  # OIDC userinfo shape
    scopes=["openid", "email", "profile"], use_pkce=True,
)
```

Override `fetch_user()` for providers whose user payload is not OIDC-shaped (see the GitHub and Discord sources).

## Plugins

27 plugins ship with the package under `better_auth.plugins_ext` (two-factor, admin,
organization, api-key, passkey, jwt, oauth-provider, sso, generic-oauth,
device-authorization, siwe, magic-link, and more; see `plugins_ext.__all__` for the
full list). Pass instances via `plugins=[...]` on `BetterAuth`. Writing your own is a
`Plugin` subclass:

```python
from better_auth import AuthResponse, Plugin

class ApiKeys(Plugin):
    id = "api-keys"
    schema = {"apikey": {...}}                      # extra tables, migrated like core ones

    def routes(self):
        return [("POST", "/api-keys/create", self.create)]

    async def create(self, ctx):
        result = await ctx.require_session()
        ...
        return {"key": "..."}

    async def before(self, ctx):                    # runs before every endpoint
        return None                                 # or AuthResponse(...) to short-circuit
```

## Security notes

- Non-GET requests are origin-checked (CSRF) against `base_url` and `trusted_origins`.
- Every `callbackURL` and `redirectTo` is validated against trusted origins, which blocks open redirects.
- Sign-in runs a dummy scrypt when the user does not exist, so unknown email and wrong password take the same time and return the same 401.
- Rate limiting defaults to in-memory, per process (`RateLimit(storage="memory")`); `"database"` and `"secondary-storage"` back it with the shared adapter or a KV store for multi-worker deployments. `ip_address=IPAddressOptions(...)` controls how the client IP is resolved from proxy headers (trusted-proxy chain, custom header list).
- `MemoryAdapter` is the default so quickstarts work. Switch to a real adapter for anything persistent.

## Roadmap

Better Auth for Python follows the TypeScript library release by release; the
[changelog](https://github.com/oumarbarry/better-auth-py/blob/main/CHANGELOG.md) tracks each catch-up against upstream releases. Still
open: CLI schema migrations. Deliberately out of scope: `open-api`,
telemetry/logger config groups, SAML, `scim`, `stripe`, and the TypeScript
`client`/expo/electron/cli packages (server-side parity only).

## Development

```bash
uv sync --all-extras
uv run pre-commit install
uv run pytest            # e2e over ASGI, both adapters, mocked OAuth
uv run ruff check .
uv run ty check
```

See [CONTRIBUTING.md](https://github.com/oumarbarry/better-auth-py/blob/main/CONTRIBUTING.md) for guidelines. Commits follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).

## For AI agents

`npx skills add oumarbarry/better-auth-py` lists the skills in this repository; pick `better-auth-py` to install it for Claude Code and compatible harnesses. It covers setup, plugins, providers and TS-to-Python migration, and every snippet in it has been executed and verified. The documentation site serves [llms.txt](https://llmstxt.org) at `/llms.txt` (index) and `/llms-full.txt` (all pages, one file). Agents contributing to this repo are governed by [AGENTS.md](https://github.com/oumarbarry/better-auth-py/blob/main/AGENTS.md); the details live on the [AI agents](https://better-auth-py.oumarbarry.dev/guide/agents) docs page.

## License

[MIT](https://github.com/oumarbarry/better-auth-py/blob/main/LICENSE). Inspired by and API-compatible with [better-auth](https://github.com/better-auth/better-auth), also MIT.
