Skip to content

Commit a369bf1

Browse files
authored
Merge pull request #149 from auth0/docs/claude-md
docs: adding repo level claude.md
2 parents ba0a2a5 + 2dc377b commit a369bf1

9 files changed

Lines changed: 628 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# AI Agent Guidelines for auth0-server-python
2+
3+
@./CLAUDE.md for all coding guidelines, commands, project structure, code style, testing conventions, and boundaries.

CLAUDE.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 on auth0-server-python — Auth0's framework-agnostic server-side authentication SDK. You work in async OIDC flows, Pydantic-typed models, and pluggable session/transaction stores that integrators supply.
8+
9+
---
10+
11+
## Working Principles
12+
13+
Apply these on every task in this repo — they keep changes correct, small, and reviewable.
14+
15+
- **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.
16+
- **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.
17+
- **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.
18+
- **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.
19+
20+
---
21+
22+
## Project Structure
23+
24+
**auth0-server-python** is the server-side (confidential client) SDK that framework wrappers such as `auth0-fastapi` build on.
25+
26+
```
27+
src/auth0_server_python/
28+
├── auth_server/
29+
│ ├── server_client.py # ServerClient — main entry point; login, session, token, passkey, CTE flows
30+
│ ├── mfa_client.py # MfaClient — MFA API; exposed via ServerClient.mfa
31+
│ └── my_account_client.py # MyAccountClient — My Account API (stateless; takes a user token per call)
32+
├── auth_schemes/
33+
│ ├── bearer_auth.py # BearerAuth — httpx.Auth strategy
34+
│ └── dpop_auth.py # DPoPAuth — RFC 9449 proofs, nonce retry, EC P-256 enforcement
35+
├── auth_types/__init__.py # All public Pydantic models + Literal type aliases
36+
├── error/__init__.py # Auth0Error hierarchy + *ErrorCode constant classes
37+
├── store/abstract.py # StateStore / TransactionStore ABCs integrators implement
38+
├── encryption/encrypt.py # JWE encrypt/decrypt (HKDF-SHA256 → A256CBC-HS512)
39+
├── utils/helpers.py # PKCE, State, URL helpers; org-claim + domain-resolver validation
40+
├── telemetry.py # Auth0-Client header construction
41+
└── tests/ # pytest suite, one test_<module>.py per module
42+
examples/ # per-feature Markdown guides (not runnable apps)
43+
references/ # Agent reference docs (this file's offloaded sections)
44+
```
45+
46+
Before working on a specific flow — interactive login, CIBA, passkeys, CTE, MFA, My Account, user linking, connected accounts, MCD — read its row in [references/flow-map.md](references/flow-map.md) for the entry points, supporting modules, and guide.
47+
48+
---
49+
50+
## Boundaries
51+
52+
### ✅ Always Do
53+
54+
- Run `poetry run pytest` and `poetry run ruff check .` before committing — both gate CI.
55+
- Add tests for new functionality, and mark async tests `@pytest.mark.asyncio` — see Common Pitfalls for why.
56+
- Raise a typed error from `error/` with a stable `code`, and re-raise the SDK's own errors untouched inside a catch-all — never return `None` or a bare `dict` to signal failure.
57+
- Return Pydantic models from `auth_types/`, validated via `model_validate` — never leak an unvalidated response `dict` through the public API.
58+
- Resolve the domain through `await self._resolve_current_domain(store_options)` and thread `store_options` through every new public flow method — reading `self._domain` breaks MCD deployments and cookie-backed stores.
59+
- Issue HTTP through `self._get_http_client()` so the `Auth0-Client` telemetry header is attached. When adding a **new outbound request path to Auth0**, route it through the existing `src/auth0_server_python/telemetry.py` mechanism rather than constructing a client or headers by hand.
60+
- Attach credentials with `BearerAuth` / `DPoPAuth` from `auth_schemes/`, not by setting `Authorization` inline.
61+
- Keep `Optional[X]` / `Union[...]` typing and `target-version = "py39"`-compatible syntax — the 3.9 CI leg fails on 3.10+ constructs.
62+
- Update `README.md` and the matching `examples/*.md` guide in the same PR when changing the public API, configuration options, or supported integration patterns.
63+
- Put new code snippets in the matching `examples/*.md` guide and link to it from `README.md` — don't add another code block to `README.md`.
64+
- Keep `.version` and `pyproject.toml`'s `version` in sync when either is touched.
65+
- Preserve existing declaration order and the `# ==== SECTION ====` banners; insert new methods into the matching section. In a file without banners, group a new method/class/function with the related code it belongs to, or append it at the end — never insert it at an arbitrary position.
66+
67+
### ⚠️ Ask First
68+
69+
- **Any breaking change — always ask first.** Never make one on your own initiative: a renamed/removed public method, a changed signature or default, a tightened model field, a new required constructor argument, or a raised Python floor.
70+
- Renaming or removing an existing `*ErrorCode` constant in `error/`, or changing the `code` an existing typed error raises. Integrators branch on `code`, so this is a breaking change even though no signature moves — treat an existing code as public API. Adding a new code is not breaking.
71+
- Adding, removing, or bumping a dependency (`pyproject.toml` + `poetry.lock` + `requirements.txt` must move together — Snyk SCA installs from `requirements.txt`).
72+
- Changing security-relevant code: token handling, `encryption/encrypt.py`, DPoP proof construction, PKCE, `state`/`nonce` handling, issuer or org-claim validation, session-expiry enforcement.
73+
- Modifying the `StateStore` / `TransactionStore` contract in `store/abstract.py`.
74+
- Changing CI/CD (`.github/workflows/`), `.ruff.toml` rule selection, or `.snyk` suppressions.
75+
- Changing the session or transaction storage format, or the store identifier defaults (`_a0_session`, `_a0_tx`).
76+
- Deprecating a public name — use the PEP 562 `__getattr__` alias pattern in `auth_types/`; don't delete it.
77+
78+
### 🚫 Never Do
79+
80+
- Commit secrets, API keys, tokens, or a real Auth0 tenant domain. Test fixtures use `<client_id>`-style placeholders and `auth0.local`.
81+
- Log, `print`, or include in an error message any token, `code`, `code_verifier`, `client_secret`, DPoP private key, or full response body. There is no logger in the SDK — don't introduce one that emits these.
82+
- Fail open. A validation, resolver, JWKS, or token-endpoint failure must raise, never fall through to a permissive default (no default domain, no "assume valid").
83+
- Validate `state` by comparing strings. `complete_interactive_login` looks the transaction up by `{transaction_identifier}:{state}` and raises `MissingTransactionError` on a miss — that store lookup *is* the binding. If you ever add a secret-to-secret comparison, use `hmac.compare_digest`, never `==`.
84+
- Run `ruff format .` repo-wide — it is not a CI gate and the tree is not format-clean, so a repo-wide run buries your change in unrelated reformatting. Format only lines you touched; `poetry run ruff format --check .` shows the current state.
85+
- Remove or `skip` a failing test instead of fixing it, or weaken an assertion to make it pass.
86+
- Hand-edit `poetry.lock`, `coverage.xml`, `CHANGELOG.md` (release flow owns it), or anything in `dist/`, `.venv/`, `__pycache__/`, `.pytest_cache/`, `.ruff_cache/`.
87+
- Break backward compatibility without asking first (see Ask First) and getting explicit approval.
88+
89+
---
90+
91+
## Security Considerations
92+
93+
This SDK is a **confidential client**: it holds a real client secret server-side and the browser never sees a token.
94+
95+
- **Tokens are server-side only.** Access, refresh, and ID tokens live in the integrator's `StateStore`; the browser holds only an opaque session reference. Never add a public accessor that hands a raw refresh token to the caller's browser layer.
96+
- **Store payloads are encrypted.** `encryption/encrypt.py` derives a key with HKDF-SHA256 from the integrator's `secret` salted with the record identifier, then produces a JWE (`alg: dir`, `enc: A256CBC-HS512`). `secret` is required at construction — `ServerClient` raises `MissingRequiredArgumentError` without it, and must keep failing closed rather than storing plaintext.
97+
- **PKCE is always on** for the authorization-code flow (`utils/helpers.py` `PKCE`, `secrets`-backed), even though this is a confidential client. `state` and `nonce` are generated per transaction and validated at callback.
98+
- **DPoP (RFC 9449)** is supported for passkey sign-in and the My Account authentication-methods/factors calls. Keys must be EC P-256/ES256 (`_validate_dpop_key` rejects anything else); resource proofs carry `ath`, token-endpoint proofs deliberately do not; a `401` + `DPoP-Nonce` triggers exactly one retry.
99+
- **Token and claim validation is mandatory.** ID-token issuer validation (`IssuerValidationError`), organization `org_id`/`org_name` claim validation (exact for `org_`-prefixed IDs, NFC-normalized case-insensitive for names), and the upstream-IdP `session_expiry` ceiling (30s skew leeway) all fail closed. Don't add a bypass flag.
100+
- **Backchannel logout** matches on `sid` **or** `sub` per the OIDC spec, and compares the token `iss` against the session's stored domain before deleting — this is what prevents cross-domain session deletion in MCD deployments.
101+
- **Secret handling.** `AUTH0_SECRET` and the client secret come from the integrator's environment/secrets manager; the SDK never reads them from disk and must never write them anywhere. `S105`/`S106` are ignored in `.ruff.toml` for kwarg names — that suppression is not permission to hardcode a value.
102+
- **Scanning:** CodeQL (`codeql.yml`) and Snyk SCA (`sca_scan.yml`) run on every PR; ruff's bandit rules (`S`) run in `ruff check .`. Report vulnerabilities via Auth0's Responsible Disclosure Program, never a public issue.
103+
104+
---
105+
106+
> The sections below are **reference** — each keeps a one-line anchor inline and offloads its body to `references/*.md`.
107+
108+
## Commands
109+
110+
```bash
111+
poetry install # setup
112+
poetry run pytest # full suite (coverage flags come from pyproject.toml)
113+
poetry run ruff check . # the lint gate in CI
114+
poetry build # sdist + wheel
115+
```
116+
117+
See [references/commands.md](references/commands.md) for the exact CI invocations, single-file/single-test runs, format and clean commands, and what deliberately doesn't exist here (no typecheck, no live-test tier). Read it when you need to run, build, or test something.
118+
119+
## Testing
120+
121+
pytest + pytest-asyncio in `src/auth0_server_python/tests/` (one `test_<module>.py` per module); the default `poetry run pytest` suite is mock-based and needs no credentials. Async tests require an explicit `@pytest.mark.asyncio`.
122+
123+
See [references/testing.md](references/testing.md) for naming, class-grouping conventions, the factory-helper pattern, mocking approach, and coverage setup. Read it before writing or restructuring tests.
124+
125+
## Code Style
126+
127+
Config is `.ruff.toml` at the repo root. CI-enforced: ruff's `E,W,F,I,B,C4,UP,S,PLC0415` rule sets — so **isort-ordered imports** (stdlib → third-party → local), **no imports inside functions** (`PLC0415`), and **bandit security rules** are hard gates. `E501` is ignored, so the `line-length = 100` is not enforced. `snake_case` methods, `PascalCase` classes, `_`-prefixed internals.
128+
129+
See [references/code-style.md](references/code-style.md) for the good/bad method examples and the dominant patterns (generic store client, `httpx.Auth` strategies, Pydantic wire contracts, PEP 562 deprecation aliases). Read it before adding a public method or a new module.
130+
131+
## Git Workflow
132+
133+
Conventional Commits preferred (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`) — nothing enforces it, history is mixed. `.github/PULL_REQUEST_TEMPLATE.md` requires a method-level inventory of what changed.
134+
135+
See [references/git-workflow.md](references/git-workflow.md) for branch naming, the full PR template requirements, and required checks. Read it when opening a PR or naming a branch.
136+
137+
## Common Pitfalls
138+
139+
The three that bite most often: the **Python 3.9 floor** (no `X | None`), **`ruff format` is not a CI gate** so don't reformat the tree, and a **missing `@pytest.mark.asyncio`** skips an async test with a warning instead of running it.
140+
141+
See [references/pitfalls.md](references/pitfalls.md) for all eight, including the MCD domain-resolver trap, the `store_options` threading requirement, and the `Literal`-vs-`str` typing rule. Read it when a change touches HTTP, domains, or type annotations.
142+
143+
## Docs Update Rules
144+
145+
> A PR that adds or changes public API, configuration, or integration patterns is **not complete** until the docs move with it.
146+
147+
Note: there is **no `EXAMPLES.md`** in this repo — per-feature guides live in `examples/*.md` instead. `CHANGELOG.md` is owned by the release flow, not by feature work.
148+
149+
See [references/docs-update.md](references/docs-update.md) for the tracked-docs inventory, the code-to-docs mapping table, and the feature → guide map. Read it whenever you change a public method, signature, config option, or error type.

0 commit comments

Comments
 (0)