Skip to content

Commit 94a5cb3

Browse files
docs: add CLAUDE.md agent guidelines for auth0-server-python
1 parent 1415ec6 commit 94a5cb3

8 files changed

Lines changed: 299 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Agent Guidelines
2+
3+
See [@CLAUDE.md](./CLAUDE.md) for the full AI-agent guidelines for this repository.
4+
5+
`CLAUDE.md` is the single source of truth — persona, working principles, boundaries, security, commands, and the `references/*.md` detail. This file exists so non-Claude agents (Codex CLI, Gemini CLI, and other tools that read `AGENTS.md`) use the same guidance.

CLAUDE.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# AI Agent Guidelines for auth0-server-python
2+
3+
This document provides context and guidelines for AI coding assistants working with the auth0-server-python codebase.
4+
5+
## Your Role
6+
7+
You are a Python SDK engineer working on auth0-server-python, Auth0's server-side authentication SDK for Python web applications. You write async-first, type-annotated code with Pydantic models and a pluggable storage abstraction, and you keep the public `ServerClient` API stable across the SDK's supported Python versions (3.9–3.12).
8+
9+
## Working Principles
10+
11+
Apply these on every task in this repo — they keep changes correct, small, and reviewable.
12+
13+
- **Think before coding.** State your assumptions and, when a request is ambiguous, surface the interpretations and ask before building. Recommend a simpler approach when you see one. A clarifying question up front beats a wrong implementation.
14+
- **Simplicity first.** Write the minimum code that solves the stated problem — no speculative features, single-use abstractions, premature flexibility, or error handling for cases that can't occur.
15+
- **Surgical changes.** Touch only what the request requires. Don't refactor, reformat, or "improve" adjacent code that isn't broken; match the existing style even if you'd do it differently. Every changed line should trace directly to the request. Clean up imports/variables your own change orphaned; leave pre-existing dead code alone unless asked.
16+
- **Goal-driven execution.** Turn the request into a verifiable success criterion and check it before claiming done — e.g. "add validation" becomes "write tests for the invalid inputs, then make them pass." Don't report success you haven't verified.
17+
18+
## Project Overview
19+
20+
**auth0-server-python** is Auth0's server-side Python SDK for implementing user authentication in Python web applications — interactive login, backchannel login/logout, token management, user linking, and connected accounts.
21+
22+
- **Language:** Python (supports 3.9–3.12)
23+
- **Tech Stack:** authlib, PyJWT + cryptography, httpx (async), Pydantic v2, jwcrypto
24+
- **Package Manager:** Poetry
25+
- **Minimum Platform Version:** Python 3.9
26+
- **Dependencies:** authlib, pyjwt, httpx, pydantic · test: pytest, pytest-asyncio, pytest-mock (full list in `pyproject.toml` — bumping a dep is Ask-First)
27+
28+
## Project Structure
29+
30+
```
31+
auth0-server-python/
32+
├── src/auth0_server_python/
33+
│ ├── auth_server/ # ServerClient (main API) + mfa_client, my_account_client
34+
│ ├── auth_schemes/ # bearer auth scheme
35+
│ ├── auth_types/ # Pydantic models / typed options
36+
│ ├── store/ # AbstractDataStore + StateStore (pluggable session/state storage)
37+
│ ├── encryption/ # encrypt/decrypt for stored state
38+
│ ├── error/ # Auth0Error hierarchy
39+
│ ├── utils/ # PKCE, State, helpers
40+
│ ├── telemetry.py # builds the Auth0-Client header
41+
│ └── tests/ # pytest suite (async)
42+
├── examples/ # hand-written usage guides (.md, one per use case)
43+
└── pyproject.toml # Poetry config, deps, pytest options
44+
```
45+
46+
### Key Files
47+
48+
| File | Purpose |
49+
|------|---------|
50+
| `src/auth0_server_python/auth_server/server_client.py` | `ServerClient` — the SDK's public API surface |
51+
| `src/auth0_server_python/store/abstract.py` | `AbstractDataStore` / `StateStore` — storage contract to implement |
52+
| `src/auth0_server_python/error/__init__.py` | `Auth0Error` exception hierarchy |
53+
| `src/auth0_server_python/telemetry.py` | `Auth0-Client` telemetry header |
54+
| `pyproject.toml` | Deps, `ruff`/`pytest` config, coverage settings |
55+
56+
## Boundaries
57+
58+
### ✅ Always Do
59+
- Run `poetry run pytest` and `poetry run ruff check .` before committing.
60+
- Add or update a test for every change (`src/auth0_server_python/tests/`).
61+
- Mark new coroutine tests with `@pytest.mark.asyncio`.
62+
- Raise typed errors from the `Auth0Error` hierarchy (`error/__init__.py`), not bare `Exception`.
63+
- Update `README.md` and the relevant `examples/*.md` in the same PR when you change the public API, configuration options, or supported integration patterns.
64+
- Update `CHANGELOG.md` for user-facing changes.
65+
66+
### ⚠️ Ask First
67+
- Adding a new dependency or bumping one in `pyproject.toml` / `poetry.lock`.
68+
- Changing the public `ServerClient` method signatures or the `AbstractDataStore` contract (breaks downstream implementers).
69+
- Dropping or changing supported Python versions (3.9–3.12 matrix).
70+
- Any breaking change to public behavior — confirm before proceeding.
71+
72+
### 🚫 Never Do
73+
- Commit secrets, client secrets, tokens, or the state-encryption `secret`/`salt`.
74+
- Log access tokens, refresh tokens, ID tokens, or the encryption secret.
75+
- Weaken token/JWT verification (signature, `iss`/`aud`/`exp` checks) to make something pass.
76+
- Skip or delete failing tests without fixing the cause.
77+
78+
## Security Considerations
79+
80+
- **Token handling:** JWTs are verified and decoded via PyJWT/jwcrypto against the tenant's JWKS (fetched and cached from OIDC metadata) — never trust an unverified token.
81+
- **State storage:** session/transaction state is encrypted before it hits the pluggable store (`encryption/encrypt.py`, AES key derived from the configured `secret` + `salt`); `ServerClient` refuses to start without a `secret`.
82+
- **PKCE:** the authorization-code flow uses PKCE (`utils.PKCE`).
83+
- **Secrets stay out of code and logs:** the encryption `secret`, client secret, and tokens are runtime inputs — never hardcode or log them.
84+
85+
---
86+
87+
> The sections below are **reference** — each keeps a one-line anchor inline and offloads its body to `references/*.md` behind a linked pointer. Read a pointer only when the task needs it.
88+
89+
## Commands
90+
91+
```bash
92+
poetry install # install deps (with dev group)
93+
poetry run pytest # run all tests (async) — safe, no credentials
94+
poetry run ruff check . # lint
95+
```
96+
97+
See [references/commands.md](references/commands.md) for the full list (coverage, single-test, format, build). Read only when you need to run, test, or build something beyond the three above.
98+
99+
## Testing
100+
101+
`poetry run pytest` runs the full suite with coverage (configured in `pyproject.toml`). Tests are async (`pytest-asyncio`) and use `unittest.mock.AsyncMock`/`pytest-mock` — the default suite is unit-only and needs no credentials or live tenant.
102+
103+
See [references/testing.md](references/testing.md) for the async test conventions, mocking approach, and coverage. Read when writing or running tests.
104+
105+
## Code Style
106+
107+
Python formatted and linted with **ruff** (line length 100, target py39). CI runs `ruff check .` and fails on violations — enabled rule sets include `E/W/F/I` (pycodestyle/pyflakes/isort), `B` (bugbear), `UP` (pyupgrade), and `S` (bandit security).
108+
109+
See [references/code-style.md](references/code-style.md) for naming, the async/typed idiom, and good/bad examples. Read when writing or reshaping code.
110+
111+
## Git Workflow
112+
113+
Branch off `main`; run `poetry run pytest` before opening a PR against `main`, following [Auth0's contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md). Every change ships with a test.
114+
115+
See [references/git-workflow.md](references/git-workflow.md) for the full contribution and PR flow. Read when preparing a PR.
116+
117+
## Common Pitfalls
118+
119+
The high-frequency traps: **forgetting `@pytest.mark.asyncio` on coroutine tests**, catching a bare `Exception` instead of an `Auth0Error` subclass, and bandit (`S`) lint failures on crypto/subprocess code.
120+
121+
See [references/pitfalls.md](references/pitfalls.md) for the full list with fixes (JWKS caching, Pydantic v2 model changes, store encryption contract). Read when a test or lint fails unexpectedly.
122+
123+
## Docs Update Rules
124+
125+
Tracked docs: `README.md` (install + getting started) and `examples/*.md` (one hand-written guide per use case — InteractiveLogin, MFA, ConnectedAccounts, UserLinking, etc.). There is no generated API-doc site — the examples are the primary reference.
126+
127+
See [references/docs-update.md](references/docs-update.md) for the code-to-docs mapping (which exported symbol maps to which doc/example). Read when changing the public API or configuration.

references/code-style.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Code Style
2+
3+
## Formatting & linting (CI-enforced)
4+
5+
- **Tool:** ruff (`.ruff.toml`), line length 100, `target-version = py39`. CI runs `ruff check .` and fails on violations.
6+
- **Enabled rule sets:** `E`/`W` (pycodestyle), `F` (pyflakes), `I` (isort — keep imports sorted/grouped), `B` (bugbear), `C4` (comprehensions), `UP` (pyupgrade — use modern Python idioms), `S` (bandit security), `PLC0415` (no imports inside functions).
7+
- **Ignored:** `E501` (line length handled separately), `B904`, `S101`/`S105`/`S106` (assert + hardcoded-password heuristics that misfire in tests).
8+
9+
## Naming & idiom
10+
11+
- Idiomatic Python: `snake_case` for functions/variables, `PascalCase` for classes, module-level constants `UPPER_SNAKE`.
12+
- The SDK is **async-first** and **type-annotated**: public methods are `async def` with full type hints; options are typed (Pydantic models in `auth_types/`, `Generic[TStoreOptions]`).
13+
- Errors come from the `Auth0Error` hierarchy in `error/` — define a new subclass rather than raising a bare `Exception`.
14+
15+
**✅ Good:**
16+
17+
```python
18+
async def get_user(self, store_options: Optional[dict[str, Any]] = None) -> Optional[dict[str, Any]]:
19+
session = await self.get_session(store_options)
20+
if session is None:
21+
return None
22+
return session.get("user")
23+
```
24+
25+
**❌ Bad:**
26+
27+
```python
28+
def get_user(self, store_options=None): # not async, no type hints
29+
session = self.get_session(store_options) # missing await on a coroutine
30+
try:
31+
return session["user"]
32+
except Exception: # bare except instead of an Auth0Error
33+
return None
34+
```
35+
36+
## Patterns
37+
38+
- **Pluggable storage:** state/session persistence goes through `AbstractDataStore` / `StateStore` — depend on the abstraction, don't hardcode a backend.
39+
- **Encrypted state:** stored state is encrypted via `encryption/encrypt.py`; don't persist plaintext session data.
40+
- **Cached OIDC/JWKS:** metadata and JWKS are fetched once and cached — reuse the cached helpers rather than re-fetching.

references/commands.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Commands
2+
3+
Canonical source: `pyproject.toml` (Poetry) and `.github/workflows/test.yml`.
4+
5+
## Everyday
6+
7+
```bash
8+
poetry install # install deps (dev group included)
9+
poetry run pytest # run all tests (async), with coverage per pyproject.toml
10+
poetry run ruff check . # lint
11+
```
12+
13+
## Coverage & single test
14+
15+
```bash
16+
# Coverage is on by default (addopts in pyproject.toml): term-missing + xml
17+
poetry run pytest -v --cov=auth0_server_python --cov-report=term-missing --cov-report=xml
18+
19+
# Run a single test file or test
20+
poetry run pytest src/auth0_server_python/tests/test_server_client.py
21+
poetry run pytest src/auth0_server_python/tests/test_server_client.py::test_init_no_secret_raises
22+
```
23+
24+
## Lint autofix & build
25+
26+
```bash
27+
poetry run ruff check . --fix # apply autofixable lint fixes
28+
poetry run ruff format . # format
29+
poetry build # build the package (sdist + wheel)
30+
```
31+
32+
CI (`test.yml`) runs `poetry run pytest` across Python 3.9–3.12 and `poetry run ruff check .`.

references/docs-update.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Docs Update Rules
2+
3+
Treat docs as part of the change. This is a **library**, so the public surface is the exported API — chiefly `ServerClient` and the `AbstractDataStore` contract. There is no generated API-doc site; `README.md` and the `examples/*.md` guides are the reference.
4+
5+
## Tracked docs
6+
7+
| Doc | Covers | Maintained |
8+
|-----|--------|-----------|
9+
| `README.md` | Install + getting started (client construction, basic login) | Hand-written |
10+
| `examples/*.md` | One guide per use case (see mapping below) | Hand-written |
11+
| `CHANGELOG.md` | User-facing changes per release | Hand-written |
12+
13+
> No `EXAMPLES.md` at the repo root — usage guides live as individual files under `examples/`.
14+
15+
## When you change code, update these docs
16+
17+
| Code change | Update |
18+
|-------------|--------|
19+
| `ServerClient` construction / config options | `README.md` "Create the Auth0 SDK client" + `examples/ConfigureStore.md` |
20+
| Interactive login (`start_/complete_interactive_login`) | `examples/InteractiveLogin.md` |
21+
| Backchannel login/logout | `examples/ClientInitiatedBackChannelLogin.md` |
22+
| MFA (`mfa_client`) | `examples/MFA.md` |
23+
| Connected accounts (`*_connect_account`, `list/delete_connected_account`) | `examples/ConnectedAccounts.md` |
24+
| User linking (`start_/complete_link_user`, unlink) | `examples/UserLinking.md` |
25+
| Token retrieval / connection tokens | `examples/RetrievingData.md`, `examples/CustomTokenExchange.md` |
26+
| Custom-domain handling | `examples/MultipleCustomDomains.md` |
27+
| Any user-facing behavior change | `CHANGELOG.md` |
28+
29+
> When you touch a public symbol that maps to a doc above, update that doc **in the same PR** — do not defer.

references/git-workflow.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Git Workflow
2+
3+
Follows [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) (referenced from `CONTRIBUTING.md`).
4+
5+
## Flow
6+
7+
1. Branch off `main` with a short, descriptive name.
8+
2. `poetry install` to set up the environment.
9+
3. Make the change; add/update a test (every change ships with a test).
10+
4. Run `poetry run pytest` and `poetry run ruff check .` locally.
11+
5. Update `CHANGELOG.md` for user-facing changes.
12+
6. Open a PR against `main` and complete the PR template.
13+
14+
## CI gates
15+
16+
`test.yml` must pass: `poetry run pytest` across Python 3.9–3.12 and `poetry run ruff check .`. CodeQL and SCA/Snyk scans also run.
17+
18+
## Releases
19+
20+
Publishing is automated via `publish.yml` (Poetry build + dynamic versioning); the version lives in `.version` / `pyproject.toml`. Don't hand-cut a release or bump the version as part of a feature PR.

references/pitfalls.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Common Pitfalls
2+
3+
- **Forgetting `@pytest.mark.asyncio`.** The SDK is async; a coroutine test without the marker is silently skipped or errors. Mark every `async def test_*`.
4+
- **Missing `await`.** `ServerClient` methods and the store contract are coroutines — a forgotten `await` returns a coroutine object, not the result. Bugbear (`B`) catches some cases, not all.
5+
- **Bare `except Exception`.** Catch/raise the specific `Auth0Error` subclass (`ApiError`, `MissingTransactionError`, `MissingRequiredArgumentError`, …) so callers can branch on `.code`. Bandit/bugbear will also flag broad excepts.
6+
- **Bandit (`S`) lint failures.** The `S` rule set scans crypto and subprocess usage. When working in `encryption/` or JWT verification, expect bandit to scrutinize it — don't silence a finding by weakening the crypto.
7+
- **Weakening JWT/JWKS verification.** Tokens are verified against the tenant's cached JWKS with issuer/audience/expiry checks. Don't disable a check to make a test pass; fix the token/fixture instead.
8+
- **Constructing `ServerClient` without a `secret`.** It raises `MissingRequiredArgumentError("secret")` by design — the secret drives state encryption. Pass one (a test value) in tests.
9+
- **Pydantic v2 semantics.** Models are Pydantic v2 (`auth_types/`); use v2 APIs (`model_dump`, `model_validate`) — not the removed v1 methods.
10+
- **Reusing OIDC/JWKS fetches.** Metadata and JWKS are cached on the client; call the cached helpers rather than adding a fresh network fetch.

references/testing.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Testing
2+
3+
## Running
4+
5+
```bash
6+
poetry run pytest
7+
```
8+
9+
Runs the full suite with coverage (configured in `pyproject.toml` `addopts`: `--cov=auth0_server_python --cov-report=term-missing:skip-covered --cov-report=xml`). No credentials or live tenant required — everything is mocked. CI runs this across Python 3.9, 3.10, 3.11, 3.12.
10+
11+
## Location & framework
12+
13+
- Tests live in `src/auth0_server_python/tests/` (`test_server_client.py`, `test_mfa_client.py`, `test_my_account_client.py`, `test_telemetry.py`).
14+
- **Framework:** pytest with `pytest-asyncio` and `pytest-mock`.
15+
16+
## Conventions
17+
18+
- The SDK is async, so most tests are coroutines — mark them with `@pytest.mark.asyncio`.
19+
- Mock collaborators with `unittest.mock.AsyncMock` / `MagicMock` (and the `mocker` fixture from `pytest-mock`); pass `AsyncMock()` for `state_store` / `transaction_store` when constructing a `ServerClient`.
20+
- Patch outbound HTTP (`httpx`) and OIDC-metadata/JWKS fetches rather than hitting the network.
21+
- Name tests for the behavior under test, e.g. `test_start_interactive_login_no_redirect_uri`, `test_init_no_secret_raises`.
22+
23+
## Mocking pattern
24+
25+
```python
26+
@pytest.mark.asyncio
27+
async def test_something(mocker):
28+
client = ServerClient(
29+
...,
30+
state_store=AsyncMock(),
31+
transaction_store=AsyncMock(),
32+
secret="test-secret",
33+
)
34+
mocker.patch.object(client, "_fetch_oidc_metadata", return_value={...})
35+
...
36+
```

0 commit comments

Comments
 (0)