auth-ai 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- auth_ai-0.1.0/.gitignore +25 -0
- auth_ai-0.1.0/CHANGELOG.md +60 -0
- auth_ai-0.1.0/LICENSE +21 -0
- auth_ai-0.1.0/PKG-INFO +345 -0
- auth_ai-0.1.0/README.md +308 -0
- auth_ai-0.1.0/examples/minimal/README.md +79 -0
- auth_ai-0.1.0/pyproject.toml +180 -0
- auth_ai-0.1.0/src/auth_ai/__init__.py +127 -0
- auth_ai-0.1.0/src/auth_ai/ai.py +101 -0
- auth_ai-0.1.0/src/auth_ai/claims.py +88 -0
- auth_ai-0.1.0/src/auth_ai/client.py +274 -0
- auth_ai-0.1.0/src/auth_ai/config.py +142 -0
- auth_ai-0.1.0/src/auth_ai/errors.py +162 -0
- auth_ai-0.1.0/src/auth_ai/gateway.py +195 -0
- auth_ai-0.1.0/src/auth_ai/gateway_errors.py +74 -0
- auth_ai-0.1.0/src/auth_ai/grants.py +212 -0
- auth_ai-0.1.0/src/auth_ai/http.py +129 -0
- auth_ai-0.1.0/src/auth_ai/id_token.py +38 -0
- auth_ai-0.1.0/src/auth_ai/introspection.py +19 -0
- auth_ai-0.1.0/src/auth_ai/jwt.py +130 -0
- auth_ai-0.1.0/src/auth_ai/messages.py +86 -0
- auth_ai-0.1.0/src/auth_ai/provider.py +114 -0
- auth_ai-0.1.0/src/auth_ai/py.typed +0 -0
- auth_ai-0.1.0/src/auth_ai/server/__init__.py +59 -0
- auth_ai-0.1.0/src/auth_ai/server/backchannel_logout.py +172 -0
- auth_ai-0.1.0/src/auth_ai/server/bearer.py +87 -0
- auth_ai-0.1.0/src/auth_ai/server/billing.py +110 -0
- auth_ai-0.1.0/src/auth_ai/server/consumption.py +135 -0
- auth_ai-0.1.0/src/auth_ai/server/machine_api.py +43 -0
- auth_ai-0.1.0/src/auth_ai/server/machine_tokens.py +65 -0
- auth_ai-0.1.0/src/auth_ai/server/protected_resource.py +70 -0
- auth_ai-0.1.0/src/auth_ai/session_user.py +46 -0
- auth_ai-0.1.0/src/auth_ai/starlette.py +121 -0
- auth_ai-0.1.0/src/auth_ai/token_endpoint.py +122 -0
- auth_ai-0.1.0/src/auth_ai/types.py +59 -0
- auth_ai-0.1.0/src/auth_ai/wallet.py +221 -0
- auth_ai-0.1.0/src/auth_ai/web/__init__.py +18 -0
- auth_ai-0.1.0/src/auth_ai/web/ai_routes.py +56 -0
- auth_ai-0.1.0/src/auth_ai/web/cookies.py +56 -0
- auth_ai-0.1.0/src/auth_ai/web/handlers.py +419 -0
- auth_ai-0.1.0/src/auth_ai/web/http.py +91 -0
- auth_ai-0.1.0/src/auth_ai/web/seal.py +59 -0
- auth_ai-0.1.0/src/auth_ai/web/sessions.py +94 -0
auth_ai-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.venv/
|
|
4
|
+
.venv*/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
.coverage.*
|
|
13
|
+
htmlcov/
|
|
14
|
+
coverage.xml
|
|
15
|
+
.env
|
|
16
|
+
.env.*
|
|
17
|
+
!.env.example
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# Editor
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/settings.json
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
Sections, when present, appear in this order: `Added`, `Changed`, `Removed`,
|
|
9
|
+
`Fixed`, `Security`. Per our no-backward-compatibility policy
|
|
10
|
+
([CONTRIBUTING.md](CONTRIBUTING.md#2-versioning-and-breaking-changes)) there is no
|
|
11
|
+
`Deprecated` section: incompatible changes go straight to `Removed` and bump the version.
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
<!-- Add user-visible changes here. Move to a dated section at release time. -->
|
|
16
|
+
|
|
17
|
+
## [0.1.0] - 2026-11-02
|
|
18
|
+
|
|
19
|
+
First release: the Python port of `@circle-rd/auth-ai` 0.1.1, with the same behaviours,
|
|
20
|
+
wire contract and error codes.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Core (`auth_ai`)**: `AuthClient` (discovery cache, PKCE login, callback with
|
|
25
|
+
ID-token verification, refresh with RFC 8707 resource narrowing, logout URL, revoke,
|
|
26
|
+
introspect, `client_credentials`), `GrantManager` (serialized refresh-token rotation,
|
|
27
|
+
per-audience access tokens, pluggable `GrantStore` and `GrantLock`), typed claims with
|
|
28
|
+
`has_role` / `has_permission` / `has_feature` / `get_feature`, the error registry
|
|
29
|
+
(`CFG_`, `OIDC_`, `TOK_`, `API_`, `GW_`, `SES_`, `REQ_`, `SRV_`) with `to_public_json()`.
|
|
30
|
+
- **Wallet and gateway**: `WalletClient` (balance, statistics, per-session usage,
|
|
31
|
+
transactions, top-up options and link; `int` micro-euros, `format_euros`),
|
|
32
|
+
`GatewayClient` (origin-locked `httpx` client, OpenAI-compatible base URL, models,
|
|
33
|
+
agents, MCP servers, MCP URLs, `post_json`, gateway error mapping),
|
|
34
|
+
`AiClient.for_user()`, and the `gateway_url` option that adds the gateway and
|
|
35
|
+
auth-service audiences and the `wallet:self:read` scope.
|
|
36
|
+
- **Server (`auth_ai.server`)**: Bearer-token verifier, back-channel logout handler with
|
|
37
|
+
replay protection, RFC 9728 protected-resource metadata and `WWW-Authenticate`
|
|
38
|
+
challenges, machine API (subscription, metered usage, invoices, consumption reporting).
|
|
39
|
+
- **Backend-for-frontend (`auth_ai.web`, `auth_ai.starlette`)**: `AuthHandlers`
|
|
40
|
+
(`/login`, `/callback`, `/me`, `/logout`, `/backchannel-logout`, optional `/wallet`,
|
|
41
|
+
`/wallet/stats`, `/models`), sealed one-shot login cookie (interoperable with the
|
|
42
|
+
TypeScript SDK), hashed session ids, `SessionStore` contract, scheduled claims
|
|
43
|
+
renewal; Starlette and FastAPI adapter with `install_auth`, `require_auth` and
|
|
44
|
+
`load_session`.
|
|
45
|
+
- Example application (`examples/minimal`) with an end-to-end self-test, and the
|
|
46
|
+
Docus documentation site.
|
|
47
|
+
|
|
48
|
+
### Security
|
|
49
|
+
|
|
50
|
+
- Every failed sign-in callback answers one generic `403 SES_005`; messages and details
|
|
51
|
+
stay in the application's logs.
|
|
52
|
+
- `__Host-` cookies, same-origin redirect targets, refused cross-site logout, URLs built
|
|
53
|
+
on the registered origin, client credentials form-urlencoded per RFC 6749 §2.3.1.
|
|
54
|
+
- JWTs signed with `EdDSA` are refused: RFC 9864 deprecates that algorithm name. The
|
|
55
|
+
fully specified `Ed25519` and `Ed448` are accepted, next to the ES\*, RS\* and PS\*
|
|
56
|
+
families (auth-service signs with ES256).
|
|
57
|
+
- A server-side failure (5xx answer) of the sign-in routes is reported to `on_error`.
|
|
58
|
+
|
|
59
|
+
[Unreleased]: https://github.com/circle-rd/auth-ai-py/compare/v0.1.0...HEAD
|
|
60
|
+
[0.1.0]: https://github.com/circle-rd/auth-ai-py/releases/tag/v0.1.0
|
auth_ai-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CIRCLE <https://circle-cyber.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
auth_ai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: auth-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for applications relying on a CIRCLE auth-service identity provider: OIDC sign-in (backend-for-frontend), entitlements, billing, credit wallet and LiteLLM gateway.
|
|
5
|
+
Project-URL: Homepage, https://github.com/circle-rd/auth-ai-py
|
|
6
|
+
Project-URL: Documentation, https://docs.circle-cyber.com/auth-ai/python
|
|
7
|
+
Project-URL: Repository, https://github.com/circle-rd/auth-ai-py
|
|
8
|
+
Project-URL: Issues, https://github.com/circle-rd/auth-ai-py/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/circle-rd/auth-ai-py/blob/main/CHANGELOG.md
|
|
10
|
+
Author: CIRCLE
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: ai-gateway,asyncio,authentication,authorization,back-channel-logout,backend-for-frontend,bff,billing,circle,credits,entitlements,fastapi,httpx,identity-provider,jwt,litellm,llm,mcp,oauth2,oauth2.1,oidc,openid-connect,pkce,rbac,self-hosted,session,sso,starlette,subscription,token-wallet,usage-based-billing
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Framework :: AsyncIO
|
|
16
|
+
Classifier: Framework :: FastAPI
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Session
|
|
26
|
+
Classifier: Topic :: Security
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.12
|
|
29
|
+
Requires-Dist: httpx>=0.28
|
|
30
|
+
Requires-Dist: joserfc>=1.7
|
|
31
|
+
Requires-Dist: pydantic>=2.12
|
|
32
|
+
Provides-Extra: fastapi
|
|
33
|
+
Requires-Dist: fastapi>=0.115; extra == 'fastapi'
|
|
34
|
+
Provides-Extra: starlette
|
|
35
|
+
Requires-Dist: starlette>=0.46; extra == 'starlette'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<picture>
|
|
40
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs-site/public/logo-dark.svg">
|
|
41
|
+
<img src="docs-site/public/logo-light.svg" alt="CIRCLE auth-ai logo" width="140" />
|
|
42
|
+
</picture>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<h1 align="center">CIRCLE auth-ai for Python</h1>
|
|
46
|
+
|
|
47
|
+
<p align="center">
|
|
48
|
+
<em>The Python SDK for CIRCLE auth-service — sign-in, entitlements, billing and LLM credit, in one library.</em>
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<p align="center">
|
|
52
|
+
<a href="https://github.com/circle-rd/auth-ai-py/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/circle-rd/auth-ai-py/actions/workflows/ci.yml/badge.svg"></a>
|
|
53
|
+
<a href="https://pypi.org/project/auth-ai/"><img alt="PyPI" src="https://img.shields.io/pypi/v/auth-ai?logo=pypi&logoColor=white"></a>
|
|
54
|
+
<a href="https://pypi.org/project/auth-ai/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/auth-ai?logo=python&logoColor=white"></a>
|
|
55
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg"></a>
|
|
56
|
+
<a href="https://mypy-lang.org"><img alt="Typed" src="https://img.shields.io/badge/typed-mypy%20strict-2a6db2.svg"></a>
|
|
57
|
+
<a href="https://github.com/circle-rd/auth-service"><img alt="auth-service" src="https://img.shields.io/badge/for-auth--service-black.svg"></a>
|
|
58
|
+
</p>
|
|
59
|
+
|
|
60
|
+
Let your application sign users in with a self-hosted
|
|
61
|
+
[CIRCLE auth-service](https://github.com/circle-rd/auth-service), read what they
|
|
62
|
+
are entitled to (roles, permissions, plan features), meter what they consume,
|
|
63
|
+
and call LLMs on their behalf through a [LiteLLM](https://docs.litellm.ai)
|
|
64
|
+
gateway that bills their credit wallet — without writing any OAuth, token
|
|
65
|
+
refresh, logout or billing plumbing yourself.
|
|
66
|
+
|
|
67
|
+
This is the Python port of [`@circle-rd/auth-ai`](https://github.com/circle-rd/auth-ai-ts):
|
|
68
|
+
same behaviours, same wire contract, same error codes. A Python backend can serve
|
|
69
|
+
the TypeScript browser client and the React and Vue bindings unchanged.
|
|
70
|
+
|
|
71
|
+
Full documentation: **https://docs.circle-cyber.com/auth-ai/python/**
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
handlers = AuthHandlers(auth=auth, grants=grants, sessions=sessions, secret=secret, ai=ai)
|
|
75
|
+
install_auth(app, handlers) # /auth/login, /callback, /me, /logout…
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.get("/api/models")
|
|
79
|
+
async def models(session: Session = Depends(require_auth(handlers))):
|
|
80
|
+
return await ai.for_user(session.user_id).gateway.list_models()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Principles
|
|
84
|
+
|
|
85
|
+
- **Do not reinvent OAuth or JOSE.** Signature, JWT and cookie encryption work stays
|
|
86
|
+
with the maintained library [joserfc](https://jose.authlib.org) and HTTP with
|
|
87
|
+
[httpx](https://www.python-httpx.org); the SDK adds what is specific to auth-service
|
|
88
|
+
on top of them. The declared direct runtime dependencies are licensed under BSD-3-Clause or MIT.
|
|
89
|
+
- **Backend-for-frontend by default.** The browser holds one opaque session
|
|
90
|
+
cookie. Access and refresh tokens never reach it.
|
|
91
|
+
- **Fail loud, leak nothing.** Invalid state is rejected, not repaired. Errors
|
|
92
|
+
carry stable codes; HTTP responses expose the code and a generic message, and
|
|
93
|
+
the detail stays in your logs.
|
|
94
|
+
- **Strict at every boundary.** Every external payload (tokens, API answers,
|
|
95
|
+
cookies, configuration) is validated with [pydantic](https://docs.pydantic.dev);
|
|
96
|
+
the package ships `py.typed` and is checked with `mypy --strict`.
|
|
97
|
+
- **Async all the way.** Built on [httpx](https://www.python-httpx.org) and
|
|
98
|
+
`asyncio`; bring your own `httpx.AsyncClient` for proxies, transports or tracing.
|
|
99
|
+
- **Centralized spending.** The SDK never debits a wallet itself: the gateway
|
|
100
|
+
bills each call, so margin and pricing stay under the administrator's control.
|
|
101
|
+
|
|
102
|
+
## Features
|
|
103
|
+
|
|
104
|
+
**Sign-in and sessions**
|
|
105
|
+
|
|
106
|
+
- Authorization-code flow with PKCE, `state` and `nonce`, multi-audience
|
|
107
|
+
tokens ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707)), TOTP handled by
|
|
108
|
+
the provider.
|
|
109
|
+
- One serialized refresh-token chain per user, with per-audience access tokens
|
|
110
|
+
minted on demand and never stored.
|
|
111
|
+
- Sealed one-shot login cookie, hashed session ids, `__Host-` cookies,
|
|
112
|
+
same-origin redirect targets, refused cross-site logout.
|
|
113
|
+
- Claims (roles, permissions, features, plan, organization) renewed from the
|
|
114
|
+
provider on a schedule.
|
|
115
|
+
- **Back-channel logout**: signature, `typ`, audience, event, lifetime and
|
|
116
|
+
replay checks, then the sessions and grant of the user end.
|
|
117
|
+
|
|
118
|
+
**Entitlements**
|
|
119
|
+
|
|
120
|
+
- Typed claims and helpers: `has_role`, `has_permission` (write implies read),
|
|
121
|
+
`has_feature`, `get_feature`.
|
|
122
|
+
- Bearer-token verification for your own API, with RFC 9728
|
|
123
|
+
protected-resource metadata and `WWW-Authenticate` challenges (MCP-friendly).
|
|
124
|
+
|
|
125
|
+
**LLM gateway and credit wallet**
|
|
126
|
+
|
|
127
|
+
- One switch in auth-service ("LiteLLM support") and one option here
|
|
128
|
+
(`gateway_url`) turn on the gateway and wallet audiences.
|
|
129
|
+
- An origin-locked `httpx` client that signs OpenAI-compatible calls with a
|
|
130
|
+
fresh user token (hand it to the `openai` package); model, agent and MCP
|
|
131
|
+
server listing; gateway errors mapped (`402` → `GW_001`, `429` with
|
|
132
|
+
`Retry-After`, …).
|
|
133
|
+
- Wallet reads: balance, spend statistics, per-session usage, transactions,
|
|
134
|
+
top-up link. Amounts are exact `int` micro-euros.
|
|
135
|
+
|
|
136
|
+
**Billing and consumption**
|
|
137
|
+
|
|
138
|
+
- Machine client (`client_credentials`, cached and single-flighted): a user's
|
|
139
|
+
subscription, metered usage and invoices; usage reporting with idempotency
|
|
140
|
+
keys for features billed per call.
|
|
141
|
+
|
|
142
|
+
**Delivery**
|
|
143
|
+
|
|
144
|
+
- Framework-agnostic `WebRequest` → `WebResponse` handlers (mount them
|
|
145
|
+
anywhere) and a Starlette / FastAPI adapter.
|
|
146
|
+
- Python 3.12+ (tested on 3.12, 3.13 and 3.14), full type declarations, MIT licensed.
|
|
147
|
+
|
|
148
|
+
## Install
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
pip install "auth-ai[fastapi]" # or auth-ai[starlette], or plain auth-ai
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Register your application in auth-service first: a confidential application with
|
|
155
|
+
the **LiteLLM support** switch on (if you call the gateway), the redirect URI
|
|
156
|
+
`https://<host>/auth/callback` and the back-channel logout URI
|
|
157
|
+
`https://<host>/auth/backchannel-logout`.
|
|
158
|
+
|
|
159
|
+
## Quick start
|
|
160
|
+
|
|
161
|
+
A FastAPI backend (the stores below live in memory: demo only, see
|
|
162
|
+
[Production](#production)):
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
import os
|
|
166
|
+
from collections.abc import AsyncIterator
|
|
167
|
+
from contextlib import asynccontextmanager
|
|
168
|
+
|
|
169
|
+
from fastapi import Depends, FastAPI
|
|
170
|
+
|
|
171
|
+
from auth_ai import AiClient, AuthClient, GrantManager, MemoryGrantStore
|
|
172
|
+
from auth_ai.starlette import install_auth, require_auth
|
|
173
|
+
from auth_ai.web import AuthHandlers, MemorySessionStore, Session
|
|
174
|
+
|
|
175
|
+
auth = AuthClient(
|
|
176
|
+
{
|
|
177
|
+
"issuer": "https://auth.example.com",
|
|
178
|
+
"client_id": "my-app",
|
|
179
|
+
"client_secret": os.environ["AUTH_CLIENT_SECRET"],
|
|
180
|
+
"redirect_uri": "https://app.example.com/auth/callback",
|
|
181
|
+
"gateway_url": "https://llm.example.com",
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
grants = GrantManager(auth=auth, store=MemoryGrantStore())
|
|
185
|
+
ai = AiClient(auth=auth, grants=grants)
|
|
186
|
+
handlers = AuthHandlers(
|
|
187
|
+
auth=auth,
|
|
188
|
+
grants=grants,
|
|
189
|
+
ai=ai, # adds /auth/wallet, /auth/wallet/stats and /auth/models for your frontend
|
|
190
|
+
sessions=MemorySessionStore(),
|
|
191
|
+
secret=os.environ["COOKIE_SECRET"], # 32+ random characters
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@asynccontextmanager
|
|
196
|
+
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
|
197
|
+
yield
|
|
198
|
+
await auth.aclose()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
app = FastAPI(lifespan=lifespan)
|
|
202
|
+
install_auth(app, handlers)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.get("/api/models")
|
|
206
|
+
async def models(session: Session = Depends(require_auth(handlers))):
|
|
207
|
+
return await ai.for_user(session.user_id).gateway.list_models()
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Calling the gateway with the `openai` package (or any OpenAI-compatible SDK that
|
|
211
|
+
takes an `httpx` client):
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from openai import AsyncOpenAI
|
|
215
|
+
|
|
216
|
+
gateway = ai.for_user(user_id).gateway
|
|
217
|
+
async with gateway.create_http_client() as http_client:
|
|
218
|
+
client = AsyncOpenAI(
|
|
219
|
+
base_url=gateway.openai_base_url,
|
|
220
|
+
api_key="unused", # replaced by a fresh user token on every request
|
|
221
|
+
http_client=http_client,
|
|
222
|
+
)
|
|
223
|
+
answer = await client.chat.completions.create(
|
|
224
|
+
model="my-model", messages=[{"role": "user", "content": "Hello"}]
|
|
225
|
+
)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Reporting usage for a feature billed per call, from your backend:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from auth_ai.server import MachineApi
|
|
232
|
+
|
|
233
|
+
machine = MachineApi(auth)
|
|
234
|
+
await machine.consumption.report(user_id, "audits", 1, idempotency_key="audit-42")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
A complete, deployable application lives in [`examples/minimal`](examples/minimal).
|
|
238
|
+
|
|
239
|
+
## Modules
|
|
240
|
+
|
|
241
|
+
| Import | What it gives you |
|
|
242
|
+
| --------------------- | --------------------------------------------------------------------------------------- |
|
|
243
|
+
| `auth_ai` | OIDC client, grants, claims helpers, wallet and gateway clients, error registry |
|
|
244
|
+
| `auth_ai.server` | Bearer verification, back-channel logout, protected-resource metadata, machine API |
|
|
245
|
+
| `auth_ai.web` | Sign-in handlers (`WebRequest` → `WebResponse`), session store contract |
|
|
246
|
+
| `auth_ai.starlette` | `install_auth`, `require_auth` and `load_session` for Starlette and FastAPI (`starlette` ≥ 0.46) |
|
|
247
|
+
|
|
248
|
+
## Architecture
|
|
249
|
+
|
|
250
|
+
```mermaid
|
|
251
|
+
sequenceDiagram
|
|
252
|
+
autonumber
|
|
253
|
+
participant B as Browser
|
|
254
|
+
participant A as Your backend (auth-ai)
|
|
255
|
+
participant S as auth-service
|
|
256
|
+
participant G as LiteLLM gateway
|
|
257
|
+
B->>A: GET /auth/login
|
|
258
|
+
A-->>B: 302 to auth-service (PKCE, resources) + sealed cookie
|
|
259
|
+
B->>S: sign in (password, TOTP…)
|
|
260
|
+
S-->>B: 302 /auth/callback?code
|
|
261
|
+
B->>A: GET /auth/callback
|
|
262
|
+
A->>S: code → tokens (client secret)
|
|
263
|
+
A-->>B: 303 + opaque session cookie
|
|
264
|
+
B->>A: GET /auth/wallet (cookie)
|
|
265
|
+
A->>S: wallet read (user token for auth-service)
|
|
266
|
+
B->>A: POST /api/chat (cookie)
|
|
267
|
+
A->>G: call (user token for the gateway)
|
|
268
|
+
G->>S: debit the wallet after the call
|
|
269
|
+
S-->>A: back-channel logout on sign-out anywhere
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
auth-ai-py/
|
|
274
|
+
├── 📁 src/auth_ai/
|
|
275
|
+
│ ├── client.py AuthClient — discovery, PKCE login, refresh, logout, revoke, introspect
|
|
276
|
+
│ ├── config.py Validated configuration (gateway_url adds the gateway audiences)
|
|
277
|
+
│ ├── grants.py GrantManager — serialized refresh rotation, per-audience tokens
|
|
278
|
+
│ ├── claims.py Claim models and entitlement helpers
|
|
279
|
+
│ ├── wallet.py WalletClient and wallet models (int micro-euros)
|
|
280
|
+
│ ├── gateway.py GatewayClient — origin-locked httpx client, listings, MCP URLs
|
|
281
|
+
│ ├── ai.py AiClient.for_user(grant_id)
|
|
282
|
+
│ ├── errors.py AuthAiError + ERR registry (OIDC_*, TOK_*, GW_*, SES_*…)
|
|
283
|
+
│ ├── 📁 server/ Bearer verification, back-channel logout, machine API
|
|
284
|
+
│ ├── 📁 web/ Sign-in handlers, sealed cookies, session store contract
|
|
285
|
+
│ └── starlette.py Starlette / FastAPI adapter
|
|
286
|
+
├── 📁 tests/ pytest suite on a scripted identity provider (no network)
|
|
287
|
+
├── 📁 examples/minimal/ Deployable end-to-end app with a self-test
|
|
288
|
+
└── 📁 docs-site/ Docus documentation (built into a container image)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Production
|
|
292
|
+
|
|
293
|
+
The SDK is storage-agnostic on purpose. Before going live:
|
|
294
|
+
|
|
295
|
+
- **Persist sessions and grants.** Implement `SessionStore` and `GrantStore` on
|
|
296
|
+
your database. The in-memory stores sign everyone out on restart and cannot
|
|
297
|
+
be shared by replicas.
|
|
298
|
+
- **Several replicas?** Provide a distributed `GrantLock` (a refresh token
|
|
299
|
+
rotates on every use; a replayed one revokes the whole family) and a shared
|
|
300
|
+
`ReplayStore` for back-channel logout tokens.
|
|
301
|
+
- **Secrets from the environment only:** the client secret and a 32+ character
|
|
302
|
+
cookie secret. Neither is ever logged or returned.
|
|
303
|
+
- **Registered URIs must match:** `redirect_uri` is `<origin><base_path>/callback`
|
|
304
|
+
and the back-channel URI is `<origin><base_path>/backchannel-logout`, reachable
|
|
305
|
+
over HTTPS by auth-service.
|
|
306
|
+
- **Log with `on_error`, answer with `to_public_json()`.** Never forward
|
|
307
|
+
`error.message` or `error.details` to a browser.
|
|
308
|
+
- **Keep your ASGI stack current:** keep `starlette` / `fastapi` on a release
|
|
309
|
+
that carries the latest security fixes.
|
|
310
|
+
|
|
311
|
+
## Documentation
|
|
312
|
+
|
|
313
|
+
The full documentation is published at
|
|
314
|
+
[docs.circle-cyber.com/auth-ai/python](https://docs.circle-cyber.com/auth-ai/python/):
|
|
315
|
+
concepts, one guide per framework, the configuration and API reference, the
|
|
316
|
+
error-code registry and a production checklist.
|
|
317
|
+
|
|
318
|
+
**For AI agents** — the documentation is exposed through a built-in MCP server
|
|
319
|
+
(HTTP), so agents can query it directly:
|
|
320
|
+
|
|
321
|
+
| Environment | MCP endpoint |
|
|
322
|
+
| ----------- | ---------------------------------------------------------------------- |
|
|
323
|
+
| Production | `https://docs.circle-cyber.com/auth-ai/python/mcp` |
|
|
324
|
+
| Local dev | `http://localhost:3000/mcp` (run `npm run dev` in `docs-site/`) |
|
|
325
|
+
|
|
326
|
+
**Contributing** — the development rules in [AGENTS.md](AGENTS.md) (naming,
|
|
327
|
+
typing strictness, error handling, testing, security) and the process in
|
|
328
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) are mandatory for any contribution.
|
|
329
|
+
Security reports: see [SECURITY.md](SECURITY.md). Releases are tracked in
|
|
330
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
331
|
+
|
|
332
|
+
## Roadmap
|
|
333
|
+
|
|
334
|
+
- **More adapters** (Django, Flask, Litestar) on top of the framework-agnostic
|
|
335
|
+
handlers, as needs appear.
|
|
336
|
+
- **A synchronous facade**, if a real need shows up.
|
|
337
|
+
|
|
338
|
+
## Credits & license
|
|
339
|
+
|
|
340
|
+
Built and maintained by [CIRCLE](https://circle-cyber.com). Stands on the
|
|
341
|
+
shoulders of [joserfc](https://jose.authlib.org),
|
|
342
|
+
[httpx](https://www.python-httpx.org), [pydantic](https://docs.pydantic.dev) and
|
|
343
|
+
[Starlette](https://www.starlette.io).
|
|
344
|
+
|
|
345
|
+
Licensed under the **MIT License** — see [LICENSE](LICENSE).
|