aauth 0.1.0__py3-none-any.whl
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.
- aauth/__init__.py +137 -0
- aauth/agent/__init__.py +2 -0
- aauth/agent/challenge_handler.py +61 -0
- aauth/agent/signer.py +78 -0
- aauth/errors.py +51 -0
- aauth/headers/__init__.py +2 -0
- aauth/headers/agent_auth.py +146 -0
- aauth/headers/signature.py +53 -0
- aauth/headers/signature_input.py +65 -0
- aauth/headers/signature_key.py +99 -0
- aauth/http/__init__.py +2 -0
- aauth/http/request.py +140 -0
- aauth/http/response.py +106 -0
- aauth/keys/__init__.py +2 -0
- aauth/keys/jwk.py +123 -0
- aauth/keys/jwks.py +191 -0
- aauth/keys/keypair.py +16 -0
- aauth/metadata/__init__.py +2 -0
- aauth/metadata/agent.py +20 -0
- aauth/metadata/auth_server.py +114 -0
- aauth/metadata/resource.py +42 -0
- aauth/resource/__init__.py +2 -0
- aauth/resource/challenge_builder.py +87 -0
- aauth/resource/token_issuer.py +74 -0
- aauth/resource/verifier.py +170 -0
- aauth/signing/__init__.py +2 -0
- aauth/signing/algorithms.py +35 -0
- aauth/signing/signature_base.py +193 -0
- aauth/signing/signer.py +138 -0
- aauth/signing/verifier.py +323 -0
- aauth/tokens/__init__.py +2 -0
- aauth/tokens/agent_token.py +202 -0
- aauth/tokens/auth_token.py +227 -0
- aauth/tokens/resource_token.py +68 -0
- aauth-0.1.0.dist-info/METADATA +584 -0
- aauth-0.1.0.dist-info/RECORD +39 -0
- aauth-0.1.0.dist-info/WHEEL +5 -0
- aauth-0.1.0.dist-info/licenses/LICENSE +21 -0
- aauth-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aauth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AAuth protocol implementation for Python
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2025 Christian Posta
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Classifier: Development Status :: 4 - Beta
|
|
28
|
+
Classifier: Intended Audience :: Developers
|
|
29
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
36
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
37
|
+
Requires-Python: >=3.9
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: cryptography>=41.0.0
|
|
41
|
+
Requires-Dist: PyJWT>=2.8.0
|
|
42
|
+
Requires-Dist: httpx>=0.25.0
|
|
43
|
+
Requires-Dist: http-message-signatures>=1.0.0
|
|
44
|
+
Requires-Dist: http-sfv>=0.9.8
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
48
|
+
Requires-Dist: fastapi>=0.104.0; extra == "dev"
|
|
49
|
+
Requires-Dist: uvicorn>=0.24.0; extra == "dev"
|
|
50
|
+
Requires-Dist: pydantic>=2.0.0; extra == "dev"
|
|
51
|
+
Requires-Dist: python-dateutil>=2.8.0; extra == "dev"
|
|
52
|
+
Requires-Dist: python-multipart>=0.0.6; extra == "dev"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
# AAuth Protocol Implementation
|
|
56
|
+
|
|
57
|
+
A Python implementation of the AAuth protocol for learning and understanding the protocol through hands-on coding.
|
|
58
|
+
|
|
59
|
+
## Library Installation
|
|
60
|
+
|
|
61
|
+
This project includes a reusable `aauth` Python library that can be installed and used in other projects.
|
|
62
|
+
|
|
63
|
+
### Installing in the Current Project
|
|
64
|
+
|
|
65
|
+
If you're working within this project:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Install in editable mode (recommended for development)
|
|
69
|
+
pip install -e .
|
|
70
|
+
|
|
71
|
+
# Or install normally
|
|
72
|
+
pip install .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Installing in Other Projects on the Same Computer
|
|
76
|
+
|
|
77
|
+
To use this library in other projects on the same computer:
|
|
78
|
+
|
|
79
|
+
1. **Navigate to your other project directory:**
|
|
80
|
+
```bash
|
|
81
|
+
cd /path/to/your/other/project
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. **Activate your project's virtual environment:**
|
|
85
|
+
```bash
|
|
86
|
+
# If using venv
|
|
87
|
+
source .venv/bin/activate # or venv/bin/activate
|
|
88
|
+
|
|
89
|
+
# If using conda
|
|
90
|
+
conda activate your-env-name
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
3. **Install the library in editable mode using the absolute path:**
|
|
94
|
+
```bash
|
|
95
|
+
pip install -e /Users/christian.posta/dev/code/aauth
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or if you're in a different location, use the full path to this project:
|
|
99
|
+
```bash
|
|
100
|
+
pip install -e /full/path/to/aauth/project
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
4. **Verify the installation:**
|
|
104
|
+
```bash
|
|
105
|
+
pip list | grep aauth
|
|
106
|
+
python -c "import aauth; print(aauth.__file__)"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The `__file__` should point to the source directory, confirming editable mode.
|
|
110
|
+
|
|
111
|
+
5. **Use the library in your code:**
|
|
112
|
+
```python
|
|
113
|
+
import aauth
|
|
114
|
+
|
|
115
|
+
# Now you can use all the library functions
|
|
116
|
+
private_key, public_key = aauth.generate_ed25519_keypair()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Note:** Editable mode (`-e`) means changes to the library source code will be immediately available in your other projects without reinstalling. This is ideal for development.
|
|
120
|
+
|
|
121
|
+
Once installed, you can import and use the library:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import aauth
|
|
125
|
+
|
|
126
|
+
# Generate a key pair
|
|
127
|
+
private_key, public_key = aauth.generate_ed25519_keypair()
|
|
128
|
+
|
|
129
|
+
# Sign a request
|
|
130
|
+
signed_headers = aauth.sign_request(
|
|
131
|
+
method="GET",
|
|
132
|
+
target_uri="https://resource.example/api/data",
|
|
133
|
+
headers={},
|
|
134
|
+
body=None,
|
|
135
|
+
private_key=private_key
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Verify a signature
|
|
139
|
+
is_valid = aauth.verify_signature(
|
|
140
|
+
method="GET",
|
|
141
|
+
target_uri="https://resource.example/api/data",
|
|
142
|
+
headers=signed_headers,
|
|
143
|
+
body=None,
|
|
144
|
+
signature_input_header=signed_headers["Signature-Input"],
|
|
145
|
+
signature_header=signed_headers["Signature"],
|
|
146
|
+
signature_key_header=signed_headers["Signature-Key"]
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
See the [Library Usage](#library-usage) section below for more examples.
|
|
151
|
+
|
|
152
|
+
## Overview
|
|
153
|
+
|
|
154
|
+
This project implements the AAuth protocol incrementally, phase by phase:
|
|
155
|
+
- **Phase 1**: Pseudonymous flow (proof-of-possession without identity)
|
|
156
|
+
- **Phase 2**: Agent identity (JWKS-based identity verification)
|
|
157
|
+
- **Phase 3**: Autonomous authorization (full token flow)
|
|
158
|
+
- **Phase 4**: User delegation (OAuth-like authorization code flow)
|
|
159
|
+
- **Phase 5**: Agent is Resource (SSO and unified token flow)
|
|
160
|
+
- **Phase 6**: Agent Delegation (agent tokens for distributed instances)
|
|
161
|
+
- **Phase 7**: Token Exchange (multi-hop resource access with delegation chain)
|
|
162
|
+
|
|
163
|
+
## Quick Start
|
|
164
|
+
|
|
165
|
+
### Setup
|
|
166
|
+
|
|
167
|
+
1. Install dependencies:
|
|
168
|
+
```bash
|
|
169
|
+
# (Recommended) Use virtual environment
|
|
170
|
+
python3 -m venv .venv
|
|
171
|
+
source .venv/bin/activate
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
pip install -r requirements.txt
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Running Demos
|
|
179
|
+
|
|
180
|
+
#### Phase 1: Pseudonymous Flow
|
|
181
|
+
|
|
182
|
+
Demonstrates basic proof-of-possession without identity using `sig=hwk` scheme:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
python demo_phase1.py
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Phase 2: Agent Identity via JWKS
|
|
189
|
+
|
|
190
|
+
Demonstrates agent identity verification using `sig=jwks` scheme:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
python demo_phase2.py
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Phase 3: Autonomous Authorization
|
|
197
|
+
|
|
198
|
+
Demonstrates complete token flow without user interaction:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
python demo_phase3.py
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Phase 4: User Delegation
|
|
205
|
+
|
|
206
|
+
Demonstrates OAuth-like authorization code flow with user consent:
|
|
207
|
+
|
|
208
|
+
**Automated mode** (uses user simulator):
|
|
209
|
+
```bash
|
|
210
|
+
python demo_phase4.py
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Manual mode** (browser-based testing):
|
|
214
|
+
```bash
|
|
215
|
+
python demo_phase4.py --manual
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Phase 5: Agent is Resource
|
|
219
|
+
|
|
220
|
+
Demonstrates agent authenticating users to itself for SSO and API access:
|
|
221
|
+
|
|
222
|
+
**Automated mode** (uses user simulator):
|
|
223
|
+
```bash
|
|
224
|
+
python demo_phase5.py
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Manual mode** (browser-based testing):
|
|
228
|
+
```bash
|
|
229
|
+
python demo_phase5.py --manual
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### Phase 6: Agent Delegation
|
|
233
|
+
|
|
234
|
+
Demonstrates agent delegation where agent servers issue agent tokens to delegates:
|
|
235
|
+
|
|
236
|
+
**Automated mode**:
|
|
237
|
+
```bash
|
|
238
|
+
python demo_phase6.py
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Phase 7: Token Exchange
|
|
242
|
+
|
|
243
|
+
Demonstrates multi-hop resource access where a resource exchanges an upstream auth token to access a downstream resource:
|
|
244
|
+
|
|
245
|
+
**Automated mode**:
|
|
246
|
+
```bash
|
|
247
|
+
python demo_phase7.py
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Testing
|
|
251
|
+
|
|
252
|
+
Run all tests:
|
|
253
|
+
```bash
|
|
254
|
+
pytest tests/ -v
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Run tests for a specific phase:
|
|
258
|
+
```bash
|
|
259
|
+
pytest tests/test_phase1.py -v
|
|
260
|
+
pytest tests/test_phase2.py -v
|
|
261
|
+
pytest tests/test_phase3.py -v
|
|
262
|
+
pytest tests/test_phase4.py -v
|
|
263
|
+
pytest tests/test_phase5.py -v
|
|
264
|
+
pytest tests/test_phase6.py -v
|
|
265
|
+
pytest tests/test_phase7.py -v
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Phase Overview
|
|
269
|
+
|
|
270
|
+
### Phase 1: Pseudonymous Flow
|
|
271
|
+
- Agent signs requests with `sig=hwk` (public key in header)
|
|
272
|
+
- Resource validates signatures
|
|
273
|
+
- No tokens, no identity - just signature verification
|
|
274
|
+
|
|
275
|
+
See [PHASE1.md](PHASE1.md) for detailed documentation.
|
|
276
|
+
|
|
277
|
+
### Phase 2: Agent Identity via JWKS
|
|
278
|
+
- Agent publishes metadata at `/.well-known/aauth-agent`
|
|
279
|
+
- Agent publishes JWKS at `/jwks.json`
|
|
280
|
+
- Resource can verify agent identity using `sig=jwks` scheme
|
|
281
|
+
- Separate endpoints (`/data-hwk`, `/data-jwks`) for both schemes
|
|
282
|
+
|
|
283
|
+
See [PHASE2.md](PHASE2.md) for detailed documentation.
|
|
284
|
+
|
|
285
|
+
### Phase 3: Autonomous Authorization
|
|
286
|
+
- Resources issue resource tokens when agents request access
|
|
287
|
+
- Agents present resource tokens to auth servers
|
|
288
|
+
- Auth servers validate resource tokens and issue auth tokens
|
|
289
|
+
- Agents use auth tokens to access protected resources
|
|
290
|
+
- Complete token flow without user interaction
|
|
291
|
+
|
|
292
|
+
See [PHASE3.md](PHASE3.md) for detailed documentation.
|
|
293
|
+
|
|
294
|
+
### Phase 4: User Delegation
|
|
295
|
+
- Auth servers issue `request_token` when user consent is required
|
|
296
|
+
- Agents redirect users to auth server's authorization endpoint
|
|
297
|
+
- Users authenticate and grant consent
|
|
298
|
+
- Auth server redirects back with authorization code
|
|
299
|
+
- Agents exchange code for auth tokens with `sub` claim
|
|
300
|
+
|
|
301
|
+
See [PHASE4.md](PHASE4.md) for detailed documentation.
|
|
302
|
+
|
|
303
|
+
### Phase 5: Agent is Resource
|
|
304
|
+
- Agent requests authorization directly with `scope` (no `resource_token`)
|
|
305
|
+
- Agent identifier matches resource identifier (agent authenticates users to itself)
|
|
306
|
+
- Auth token has `aud` = agent identifier and `agent` claim omitted
|
|
307
|
+
- Unified token serves both SSO (user identity) and API access purposes
|
|
308
|
+
- Solves OIDC limitation where ID tokens and access tokens are separate
|
|
309
|
+
|
|
310
|
+
See [PHASE5.md](PHASE5.md) for detailed documentation.
|
|
311
|
+
|
|
312
|
+
### Phase 6: Agent Delegation
|
|
313
|
+
- Agent servers issue agent tokens (`agent+jwt`) to agent delegates
|
|
314
|
+
- Delegates use `scheme=jwt` with agent tokens to sign requests
|
|
315
|
+
- Resources and auth servers validate agent tokens per SPEC.md Section 5.7
|
|
316
|
+
- Delegates share agent server's identity but use ephemeral keys
|
|
317
|
+
- Delegate identifier (`sub`) persists across key rotations
|
|
318
|
+
- Auth tokens include `agent_delegate` claim when issued to delegates
|
|
319
|
+
|
|
320
|
+
See [PHASE6.md](PHASE6.md) for detailed documentation.
|
|
321
|
+
|
|
322
|
+
### Phase 7: Token Exchange
|
|
323
|
+
- Resources can act as agents to access downstream resources
|
|
324
|
+
- Upstream auth tokens are exchanged for downstream auth tokens
|
|
325
|
+
- Downstream auth server validates upstream token and federation trust
|
|
326
|
+
- Exchanged tokens include `act` claim showing the delegation chain
|
|
327
|
+
- User context (`sub`) is preserved through the chain
|
|
328
|
+
- Enables autonomous multi-hop resource access
|
|
329
|
+
|
|
330
|
+
See [PHASE7.md](PHASE7.md) for detailed documentation.
|
|
331
|
+
|
|
332
|
+
## Running Individual Participants
|
|
333
|
+
|
|
334
|
+
### Run Resource Server:
|
|
335
|
+
```bash
|
|
336
|
+
python -m participants.resource
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Run Agent:
|
|
340
|
+
```bash
|
|
341
|
+
python -m participants.agent
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Run Auth Server:
|
|
345
|
+
```bash
|
|
346
|
+
python -m participants.auth_server
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Library Usage
|
|
350
|
+
|
|
351
|
+
The `aauth` library provides a framework-agnostic implementation of the AAuth protocol. Here are some common usage patterns:
|
|
352
|
+
|
|
353
|
+
### Key Generation
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
import aauth
|
|
357
|
+
|
|
358
|
+
# Generate Ed25519 key pair
|
|
359
|
+
private_key, public_key = aauth.generate_ed25519_keypair()
|
|
360
|
+
|
|
361
|
+
# Convert to JWK format
|
|
362
|
+
jwk = aauth.public_key_to_jwk(public_key, kid="key-1")
|
|
363
|
+
|
|
364
|
+
# Calculate JWK thumbprint
|
|
365
|
+
thumbprint = aauth.calculate_jwk_thumbprint(jwk)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### HTTP Message Signing
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
import aauth
|
|
372
|
+
|
|
373
|
+
# Sign a request with hwk scheme (pseudonymous)
|
|
374
|
+
signed_headers = aauth.sign_request(
|
|
375
|
+
method="GET",
|
|
376
|
+
target_uri="https://resource.example/api/data",
|
|
377
|
+
headers={"Host": "resource.example"},
|
|
378
|
+
body=None,
|
|
379
|
+
private_key=private_key,
|
|
380
|
+
sig_scheme="hwk"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Sign with jwks scheme (agent identity)
|
|
384
|
+
signed_headers = aauth.sign_request(
|
|
385
|
+
method="POST",
|
|
386
|
+
target_uri="https://resource.example/api/data",
|
|
387
|
+
headers={"Content-Type": "application/json"},
|
|
388
|
+
body=b'{"key": "value"}',
|
|
389
|
+
private_key=private_key,
|
|
390
|
+
sig_scheme="jwks",
|
|
391
|
+
id="https://agent.example",
|
|
392
|
+
kid="key-1"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Sign with jwt scheme (using auth token)
|
|
396
|
+
signed_headers = aauth.sign_request(
|
|
397
|
+
method="GET",
|
|
398
|
+
target_uri="https://resource.example/api/data",
|
|
399
|
+
headers={},
|
|
400
|
+
body=None,
|
|
401
|
+
private_key=private_key,
|
|
402
|
+
sig_scheme="jwt",
|
|
403
|
+
jwt=auth_token
|
|
404
|
+
)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Signature Verification
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
import aauth
|
|
411
|
+
|
|
412
|
+
# Verify a signature
|
|
413
|
+
is_valid = aauth.verify_signature(
|
|
414
|
+
method=request.method,
|
|
415
|
+
target_uri=str(request.url),
|
|
416
|
+
headers=dict(request.headers),
|
|
417
|
+
body=request_body,
|
|
418
|
+
signature_input_header=request.headers.get("Signature-Input"),
|
|
419
|
+
signature_header=request.headers.get("Signature"),
|
|
420
|
+
signature_key_header=request.headers.get("Signature-Key"),
|
|
421
|
+
jwks_fetcher=my_jwks_fetcher # Required for jwks/jwt schemes
|
|
422
|
+
)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Token Creation and Validation
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
import aauth
|
|
429
|
+
|
|
430
|
+
# Create a resource token
|
|
431
|
+
resource_token = aauth.create_resource_token(
|
|
432
|
+
iss="https://resource.example",
|
|
433
|
+
aud="https://auth.example",
|
|
434
|
+
agent="https://agent.example",
|
|
435
|
+
agent_jkt=agent_thumbprint,
|
|
436
|
+
scope="data.read data.write",
|
|
437
|
+
private_key=resource_private_key,
|
|
438
|
+
kid="resource-key-1"
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Create an auth token
|
|
442
|
+
auth_token = aauth.create_auth_token(
|
|
443
|
+
iss="https://auth.example",
|
|
444
|
+
aud="https://resource.example",
|
|
445
|
+
agent="https://agent.example",
|
|
446
|
+
cnf_jwk=agent_jwk,
|
|
447
|
+
scope="data.read",
|
|
448
|
+
private_key=auth_private_key,
|
|
449
|
+
kid="auth-key-1"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Verify an agent token
|
|
453
|
+
claims = aauth.verify_agent_token(
|
|
454
|
+
token=agent_token,
|
|
455
|
+
jwks_fetcher=my_jwks_fetcher,
|
|
456
|
+
expected_aud="https://resource.example"
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Parse token claims (without verification)
|
|
460
|
+
claims = aauth.parse_token_claims(token)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Agent-Auth Header Parsing
|
|
464
|
+
|
|
465
|
+
```python
|
|
466
|
+
import aauth
|
|
467
|
+
|
|
468
|
+
# Parse Agent-Auth challenge header
|
|
469
|
+
challenge = aauth.parse_agent_auth_header(
|
|
470
|
+
"httpsig; identity=?1; auth-token; resource_token=\"...\"; auth_server=\"https://auth.example\""
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
# Build Agent-Auth challenge
|
|
474
|
+
challenge_header = aauth.build_agent_auth_challenge(
|
|
475
|
+
require_signature=True,
|
|
476
|
+
require_identity=True,
|
|
477
|
+
require_auth_token=True,
|
|
478
|
+
resource_token=resource_token,
|
|
479
|
+
auth_server="https://auth.example"
|
|
480
|
+
)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### High-Level Agent and Resource APIs
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
import aauth
|
|
487
|
+
|
|
488
|
+
# Agent request signer
|
|
489
|
+
signer = aauth.AgentRequestSigner(
|
|
490
|
+
private_key=private_key,
|
|
491
|
+
agent_id="https://agent.example",
|
|
492
|
+
agent_token=agent_token
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
signed_headers = signer.sign_request(
|
|
496
|
+
method="GET",
|
|
497
|
+
target_uri="https://resource.example/api/data",
|
|
498
|
+
headers={},
|
|
499
|
+
body=None,
|
|
500
|
+
sig_scheme="jwt"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# Resource request verifier
|
|
504
|
+
verifier = aauth.RequestVerifier(
|
|
505
|
+
canonical_authorities=["resource.example:443"],
|
|
506
|
+
jwks_fetcher=my_jwks_fetcher
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
result = verifier.verify_request(
|
|
510
|
+
method="GET",
|
|
511
|
+
target_uri="https://resource.example/api/data",
|
|
512
|
+
headers=request_headers,
|
|
513
|
+
body=request_body,
|
|
514
|
+
require_identity=True,
|
|
515
|
+
require_auth_token=True
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
if result["valid"]:
|
|
519
|
+
print(f"Agent ID: {result['agent_id']}")
|
|
520
|
+
print(f"Scopes: {result['scopes']}")
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Framework Integration
|
|
524
|
+
|
|
525
|
+
The library is framework-agnostic. Here's an example for FastAPI:
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
from fastapi import FastAPI, Request
|
|
529
|
+
from aauth import RequestVerifier, AAuthRequest
|
|
530
|
+
|
|
531
|
+
app = FastAPI()
|
|
532
|
+
verifier = RequestVerifier(canonical_authorities=["api.example.com"])
|
|
533
|
+
|
|
534
|
+
@app.get("/protected")
|
|
535
|
+
async def protected_endpoint(request: Request):
|
|
536
|
+
# Convert FastAPI request to AAuthRequest
|
|
537
|
+
aauth_req = AAuthRequest.from_fastapi_request(request)
|
|
538
|
+
|
|
539
|
+
# Verify signature
|
|
540
|
+
result = verifier.verify_request(
|
|
541
|
+
method=aauth_req.method,
|
|
542
|
+
target_uri=str(request.url),
|
|
543
|
+
headers=dict(request.headers),
|
|
544
|
+
body=await request.body()
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
if not result["valid"]:
|
|
548
|
+
return {"error": result["error"]}, 401
|
|
549
|
+
|
|
550
|
+
return {"data": "protected resource", "agent": result["agent_id"]}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## Project Structure
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
aauth/
|
|
557
|
+
├── aauth/ # Main library package (installable)
|
|
558
|
+
│ ├── signing/ # HTTP Message Signing (RFC 9421)
|
|
559
|
+
│ ├── keys/ # Key management and JWK operations
|
|
560
|
+
│ ├── tokens/ # JWT token handling
|
|
561
|
+
│ ├── headers/ # HTTP header parsing/building
|
|
562
|
+
│ ├── metadata/ # Metadata discovery
|
|
563
|
+
│ ├── http/ # HTTP abstraction layer
|
|
564
|
+
│ ├── agent/ # Agent role implementation
|
|
565
|
+
│ └── resource/ # Resource role implementation
|
|
566
|
+
├── core/ # Legacy core utilities (deprecated, use aauth.*)
|
|
567
|
+
├── participants/ # Protocol participants (demo implementations)
|
|
568
|
+
├── flows/ # Flow implementations
|
|
569
|
+
└── tests/ # Test suite
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
## Documentation
|
|
573
|
+
|
|
574
|
+
- [PHASE1.md](PHASE1.md) - Phase 1 implementation details
|
|
575
|
+
- [PHASE2.md](PHASE2.md) - Phase 2 implementation details
|
|
576
|
+
- [PHASE3.md](PHASE3.md) - Phase 3 implementation details
|
|
577
|
+
- [PHASE4.md](PHASE4.md) - Phase 4 implementation details
|
|
578
|
+
- [PHASE5.md](PHASE5.md) - Phase 5 implementation details
|
|
579
|
+
- [PHASE6.md](PHASE6.md) - Phase 6 implementation details
|
|
580
|
+
- [PHASE7.md](PHASE7.md) - Phase 7 implementation details
|
|
581
|
+
- [SPEC.md](SPEC.md) - AAuth protocol specification
|
|
582
|
+
- [PLAN.md](PLAN.md) - Overall implementation plan
|
|
583
|
+
|
|
584
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
aauth/__init__.py,sha256=GQE7QByMM-JuWZiAIkdsydeW2aqAXmTh9zkuD-5hRO0,3449
|
|
2
|
+
aauth/errors.py,sha256=M9EQW4MckG5_O9aET1UGrH2CzKZ0p1G0pU_rL3TVUII,1477
|
|
3
|
+
aauth/agent/__init__.py,sha256=p5u-g-n42RGvqqwGDmXawl7MKJNFf5XBM12P1PH36DU,44
|
|
4
|
+
aauth/agent/challenge_handler.py,sha256=Ak1cWhTpEtenUrDEzyn_VjInJlpv_CEwFxR8WJrdwM0,1900
|
|
5
|
+
aauth/agent/signer.py,sha256=Vtc4r497xqePnpiG2ezSs_0ovoIQlA5fNfw8jtSA1xk,2330
|
|
6
|
+
aauth/headers/__init__.py,sha256=qDWUmZUixl_ENKPacby5cbchTT_3elLMTYDYqt5lqyw,51
|
|
7
|
+
aauth/headers/agent_auth.py,sha256=0O5W--dsrXICbBWPzfOLpYQYdkIoJv621MwprG8db04,5071
|
|
8
|
+
aauth/headers/signature.py,sha256=oNKXzdRs_lJjJVGXyVB1c9moxHJG378UGYhVLTXbmek,1586
|
|
9
|
+
aauth/headers/signature_input.py,sha256=HrawyYSbTDyyFl8j641NulEqRmmbeuut4wGBnfM5wE0,1873
|
|
10
|
+
aauth/headers/signature_key.py,sha256=CsnGuYdqV8IaaT0zhZlSXLcXxoIzjiDt_I6xUYHQZsM,3330
|
|
11
|
+
aauth/http/__init__.py,sha256=FfXVAUrwPj8q1eYdfFEuLtx3xZ-P2lRrcznBvqBsVXM,71
|
|
12
|
+
aauth/http/request.py,sha256=MBvLapZ5GA-uawZkEBRzzx4k0Dst_K_m7VosQyKMIFg,4597
|
|
13
|
+
aauth/http/response.py,sha256=LNpjSQV5bp1iZAVazJK3o_ozj66Z7aPC41im0JMDy_A,3238
|
|
14
|
+
aauth/keys/__init__.py,sha256=i7LtjgPFMa2kxhfqfwU7_UXewogN5mt1Nr1vgOShjVg,52
|
|
15
|
+
aauth/keys/jwk.py,sha256=qEoUbeAu6_Uu0GmUBkrJkmqbrqFPwjjeQ__TxIV8Lh8,3346
|
|
16
|
+
aauth/keys/jwks.py,sha256=k8LeFuzdCCdRjNL2wF8B7gckn4ZdYqdAGx9M2dJoSyA,5744
|
|
17
|
+
aauth/keys/keypair.py,sha256=8gDGmabTLGJklZxs5BaR07YH2JTpJiVteWjCqevbVaQ,475
|
|
18
|
+
aauth/metadata/__init__.py,sha256=7VGwitDkhBld8AncX9XojSlDV5SMJuQAoPbHbRfqTUE,37
|
|
19
|
+
aauth/metadata/agent.py,sha256=OLbtMrzavhyHxDB5gc866BXG76Exw2cycfmejHyz56g,487
|
|
20
|
+
aauth/metadata/auth_server.py,sha256=SuqvpJS2BVPYW_tdKaZROSkpl7cIPSeGhKkrWqUaipY,3813
|
|
21
|
+
aauth/metadata/resource.py,sha256=wHefA_J9eR_3iHVuFp5zVufvKqOmw5C67vH_GTEdHD4,1396
|
|
22
|
+
aauth/resource/__init__.py,sha256=TSeRABdTS7T9FzYgDgHNF8gRRvjmTp8Qe-eg6iF09zI,47
|
|
23
|
+
aauth/resource/challenge_builder.py,sha256=tSjFwB8auNODa4ygTepOonWzd244d32ZG7qCeeQp_QA,3032
|
|
24
|
+
aauth/resource/token_issuer.py,sha256=h1qXbuWvE-MARIH1Bjqe1ZWAw1kVGp9HX-RJ3yWDik0,2272
|
|
25
|
+
aauth/resource/verifier.py,sha256=DEcYna-_690tJCcoo8l-3-0zPVeTVjbUH7xrxAtuEwA,6128
|
|
26
|
+
aauth/signing/__init__.py,sha256=8UE9vvUWvh-y6amcxWF8p4SS39TGEXCJUDupxvhL6tA,50
|
|
27
|
+
aauth/signing/algorithms.py,sha256=w3IIDdc-PH99UJLbKjGgsFwU9GEWleOhaDu-0miDDPM,799
|
|
28
|
+
aauth/signing/signature_base.py,sha256=zrTnVpGZfYObaENSiWE7eB-JZ2p4YUoxFuH7IXnPaTM,6950
|
|
29
|
+
aauth/signing/signer.py,sha256=1K-pBJo1SufzkjFhYjMDQ9Ud9YEaH_NBDC8Cb1SFpXk,5806
|
|
30
|
+
aauth/signing/verifier.py,sha256=AKfn7Dkyoaf6XKhOMGgUzaLtxR1ztTSBbrPi25qqhck,13428
|
|
31
|
+
aauth/tokens/__init__.py,sha256=fhbhS240qSnr6txsHa1YTU6mXLVKZwkimmYW2WiBAg4,37
|
|
32
|
+
aauth/tokens/agent_token.py,sha256=lhnOaLnIdvozX1Hg0FEL91fzWu-Lk7GxYKZ2wPllCdQ,6025
|
|
33
|
+
aauth/tokens/auth_token.py,sha256=ZijY9Jz3KhAKzOKCasK46FmHkov-bOlPQeVl245EgTw,6797
|
|
34
|
+
aauth/tokens/resource_token.py,sha256=N8qYtrKwxYkfoPqzOZy-ELjoMXeBhIpUPYC4d1ECL5U,1736
|
|
35
|
+
aauth-0.1.0.dist-info/licenses/LICENSE,sha256=_WtxkxhY75NoyYzG8CpktAzxKepoNhUVa7ATMHjpDUQ,1072
|
|
36
|
+
aauth-0.1.0.dist-info/METADATA,sha256=gRAnqXQo_SaomjNQU2kkqr-awAOgER1lvVmEHtNicoU,16647
|
|
37
|
+
aauth-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
38
|
+
aauth-0.1.0.dist-info/top_level.txt,sha256=9BtzE6AQX9xKQoa53QIJpZyT0Dx3O0CdFU8Pmy8z7kI,6
|
|
39
|
+
aauth-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Christian Posta
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aauth
|