feat(standards): integrate auth-server via pinned spec (docs://spec) + auth recipe and template slice

- serve the pinned auth-server OpenAPI contract as the standards MCP resource docs://spec (no auth-server MCP bundle)
- add Auth Integration recipe (14) and wire it into the registry, prompts, index, and nav
- un-defer Auth Server in the library registry (spec/recipe pointers)
- ship the opt-in template auth slice: interfaces/auth/ (placeholder + mergeable bearerAuth security.yaml) and AUTH_SERVER/JWT_SERVER env
- rebuild bundle (13 modules, 44 components); template stays authless by default
This commit is contained in:
2026-10-14 16:18:14 +05:30
parent 6c0d859a1e
commit 18d52bdb7c
37 changed files with 2094 additions and 24 deletions

View File

@@ -24,7 +24,7 @@ can read a library's full API by connecting to its MCP and reading
| Mail Intake | `mail_intake` | Provider-agnostic read-only email ingestion | Gmail OAuth + MIME parsing | `:8002` |
| Py JWT | `jwtlib` | JWT auth primitives (register/login/introspect) | password hashing + token code | `:8005` |
| Doc Forge | `docforge` | Docstring compiler → MkDocs + MCP docs | custom doc generators | `:8001` |
| Auth Server | *(service)* | Central identity service (register/login/me/introspect) | building auth endpoints yourself | *(deferred)* |
| Auth Server | *(service)* | Central identity service (register/login/me/logout/introspect) | building auth endpoints yourself | spec: `docs://spec` |
## How to choose
@@ -43,7 +43,9 @@ can read a library's full API by connecting to its MCP and reading
A single **standards MCP** exposes `find_library(capability)`, which returns the
matching library, its import name, its "use instead of" guidance, and its MCP
endpoint. Query it before writing any infrastructure.
endpoint (or, for the auth-server service, a spec pointer). It also serves the
pinned auth-server OpenAPI contract at `docs://spec`. Query it before writing
any infrastructure.
## Notes
@@ -51,7 +53,10 @@ endpoint. Query it before writing any infrastructure.
`docs://index`, `docs://nav`, and `docs://modules/{module}` (e.g.
`docs://modules/hexa.container`) to discover the DI surface — `build(cls, *,
instances, config)` and the `PortNode` model.
- **Auth Server** is deferred: it has no `pyproject.toml`/importable package and
no MCP bundle. Do not reach for it yet; `py-jwt` covers token primitives.
- **Auth Server** is a **service**, not an importable library: it has no MCP
bundle by design. Read its client contract from the pinned OpenAPI document
via `docs://spec` and wire it by declaring a `bearerAuth` security scheme in
the app's own spec (see **Auth Integration**); `py-jwt` covers the
low-level token path.
- Library MCP servers expose **documentation resources** (not executable tools):
they tell an agent the available API surface so it does not invent code.

View File

@@ -0,0 +1,140 @@
# Auth Integration
## Summary
Authentication is a **service**, not a library. Every Aetos application shares a
single identity service — the **auth-server** — and consumes it through its HTTP
contract. An app never implements registration, login, password hashing, or
token issuance itself.
The agent does **not** need an MCP bundle for the auth-server. It reads the
pinned OpenAPI contract (below) and declares a bearer security scheme in its own
spec; `openapi_first` then generates the verifying dependency. **No auth code is
written.**
---
## Read the contract
The auth-server OpenAPI document is pinned in the standards bundle and served as
an MCP resource:
```
docs://spec
```
It is the authoritative client contract (OpenAPI 3.1). Read it to learn the
endpoints and the request/response schemas:
| Endpoint | Purpose |
|---|---|
| `POST /register` | Create a user (201, returns `PublicUser`) |
| `POST /login` | Authenticate and issue a JWT (`LoginResponse`) |
| `GET /me` | Current user's `PublicUser` (bearer required) |
| `POST /logout` | Stateless logout (`LogoutResponse`) |
| `POST /introspect` | Internal token introspection (`active`, `user`) |
The pinned snapshot is versioned (currently `0.0.5`). If the caller has a live
auth-server and needs the *current* contract, they can pass the full endpoint
(`{AUTH_SERVER}/openapi.json`) and read it directly instead of the pin.
---
## Wire it up: declare, don't code
Authentication is added by editing the app's **own** `openapi.yaml` — nothing
else. Declare a global bearer requirement and point the scheme at the auth
server (values come from the environment):
```yaml
security:
- bearerAuth: []
paths:
# ...
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-server-url: "{AUTH_SERVER}"
x-introspect-path: "/introspect"
x-login-path: "/login"
x-register-path: "/register"
x-logout-path: "/logout"
x-me-path: "/me"
```
`openapi_first` reads `securitySchemes` + `security` and generates a FastAPI
dependency that extracts the bearer token, POSTs `{"token": <jwt>}` to
`{AUTH_SERVER}/introspect`, and returns the user when `active == true`. It
raises `401` on an invalid or inactive token and `503` when the auth server is
unreachable. The generated handlers receive the authenticated user — you author
**no** authentication code.
### Public operations
A route that must stay open opts out by overriding `security` with an empty
list:
```yaml
paths:
/health:
get:
operationId: health
security: [] # overrides the global requirement
responses:
"200":
description: OK
```
### Environment
```bash
AUTH_SERVER=http://localhost:8000 # auth-server base URL
JWT_SERVER=http://localhost:8000 # jwtlib client base URL (seeding/CLI)
```
---
## Path 1: `jwtlib` client (no generator)
Apps that do not use a generated dependency verify requests with the `py-jwt`
client:
```python
from jwtlib.introspection import authenticate_request
authorized = await authenticate_request(
should_skip_authentication, # allow public paths
method,
path,
authorization_token, # "Bearer <jwt>" or None
)
```
`authenticate_request` POSTs to `{JWT_SERVER}/introspect` (3 second timeout) and
treats `{"active": true, "user": {...}}` as valid. Use this for seeding scripts
and tests (`jwtlib.register_user`, `jwtlib.login_user`).
---
## Golden rules
1. **Never trust an unverified token.** Decode through the generated dependency
or `jwtlib` before handling the request.
2. **Treat `active: false` as unauthenticated**, even if the JWT decodes.
3. **Do not implement your own JWT parsing.** Revocation and issuer changes must
stay centralized in the auth-server.
4. **Do not add auth until the app's outcome is validated.** Ship the app
authless first; layer the security scheme once the core behaviour works.
---
## Notes
- The default template is **authless** on purpose. Add the `securitySchemes`
block above only when auth is required. Pull the shared slice with
`get_app_component("auth")`.
- The auth-server is deployed as a service (no importable package); the client
surface is the spec, plus `jwtlib` for the low-level path.

View File

@@ -15,6 +15,8 @@ patterns, and the internal library catalog that agents consult while coding.
- **Human-in-the-Loop** — guardrails and approvals inside automations.
- **Infra Adapters** — how infrastructure connects without leaking into domain.
- **Genericity Matrix** — what is library-backed, template-provided, or authored.
- **Auth Integration** — consuming the shared auth-server service (spec-driven,
no auth code).
- **Library Registry** — the catalog of internal libraries behind all of it.
Read the **Wiki** for the philosophy and rationale; these pages are the

View File

@@ -64,4 +64,5 @@ nav:
- Human-in-the-Loop: 08_human_in_loop.md
- Infra Adapters: 09_infra_adaptors.md
- Genericity Matrix: 12_genericity_matrix.md
- Auth Integration: 14_auth_integration.md
- Library Registry: 13_library_registry.md

View File

@@ -2,4 +2,6 @@ DEBUG=false
MONGO_URI=mongodb://localhost:27017
MONGO_DB=app_template
WORKER_POLL_SECONDS=30
WORKER_MAX_RETRIES=3
WORKER_MAX_RETRIES=3
AUTH_SERVER=http://localhost:8000
JWT_SERVER=http://localhost:8000

View File

@@ -0,0 +1,9 @@
"""# Summary
Auth interface: thin FastAPI entry points related to authentication, wired to
openapi.yaml operationIds by the OpenAPI-first router.
Authentication itself is provided by the shared auth-server service. Wire it by
declaring a ``bearerAuth`` security scheme in ``openapi.yaml`` (see
``security.yaml``) and reading the contract at ``docs://spec``.
"""

View File

@@ -0,0 +1,14 @@
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-server-url: "{AUTH_SERVER}"
x-introspect-path: "/introspect"
x-login-path: "/login"
x-register-path: "/register"
x-logout-path: "/logout"
x-me-path: "/me"