- Issuer
- Module / factory
- OAuth OBO (any standards-conformant issuer)
- OAuth OBO
- Microsoft Entra ID / Entra Agent ID
- Microsoft Entra ID / Entra Agent ID
- Auth0 / Auth0 for AI Agents
- Auth0 / Auth0 for AI Agents
Specs
- IssuerModule / factory
- OAuth OBO (any standards-conformant issuer)OAuth OBO
- Microsoft Entra ID / Entra Agent IDMicrosoft Entra ID / Entra Agent ID
- Auth0 / Auth0 for AI AgentsAuth0 / Auth0 for AI Agents
- FailureStatus
- Missing / malformed Authorization header401
- Token issuer not recognised401
- Bad signature / expired / malformed token403
- Missing required scope403
- VersionScope
- v0.1v0.1
- v0.2TypeScript SDK at parity; WorkOS profile
- v0.3A2A Agent Card adapter (post-spec freeze); Express middleware
- v0.5Optional hosted audit-log service; W3C VC adapter
- v1.0Third-party security audit
- PackageLanguage
- demarchePython ≥3.10
- @deeplethe/demarcheTypeScript ≥20
README
Demarche
Vendor-neutral identity verification for AI agents.
Demarche sits between your application and the growing ecosystem of
agent-identity issuers — Microsoft Entra Agent ID, Auth0 for AI Agents,
WorkOS, OpenAgents, any OAuth On-Behalf-Of issuer. Integrate once,
verify agents from any issuer, with no vendor lock-in.
An agent acts in your démarche: a formal action taken on your behalf,
with verifiable authority. Demarche makes that verification a one-line
integration.
from typing import Annotated
from fastapi import Depends, FastAPI
from demarche import Verifier, VerificationResult, entra_agent_id
from demarche.fastapi import DemarcheAuth
verifier = Verifier(adapters=[
entra_agent_id(tenant_id="<your-tenant>", audience="api://your-app"),
])
auth = DemarcheAuth(verifier)
app = FastAPI()
@app.get("/whoami")
async def whoami(
agent: Annotated[VerificationResult, Depends(auth.require_agent)],
):
return {
"user": agent.principal_id, # who delegated authority
"agent": agent.agent_id, # which agent is acting
"scopes": list(agent.scopes),
}
Status
Pre-alpha — v0.0.0 published on PyPI and npm as of 2026-05-18.
Public API will likely change before the v0.1.0 cut; pin exact
versions if you adopt early. 83 tests passing, ruff clean, mypy
strict clean. Architecture contract in
spec/v0.1-architecture.md.
Why Demarche
In 2026 the agent-identity ecosystem has fragmented — Microsoft, Auth0,
WorkOS, Google A2A, OpenAgents, IETF OAuth OBO drafts, W3C VC — and a
scan of ~2,000 public MCP servers found all of them lacked
authentication. The protocols exist; integrating N issuers requires
N SDKs with N shapes. Demarche pays that cost once on behalf of
integrators.
We are to agent identity what Plaid is to bank connections: a neutral
normalisation layer over a fragmented ecosystem.
Supported issuers (v0.1)
| Issuer | Module / factory |
|---|---|
| OAuth OBO (any standards-conformant issuer) | OAuthOBOAdapter |
| Microsoft Entra ID / Entra Agent ID | entra_agent_id(tenant_id=..., audience=...) |
| Auth0 / Auth0 for AI Agents | auth0_ai_agents(domain=..., audience=...) |
Planned for v0.2+: Google A2A (post-v0.9 signed-Agent-Card freeze),
WorkOS, OpenAgents, W3C VC. Track in issues.
Install
pip install demarche # core + OAuth OBO + Entra/Auth0
pip install 'demarche[fastapi]' # + FastAPI integration
npm install @deeplethe/demarche # TypeScript (placeholder; parity in v0.2)
The npm package is scoped as @deeplethe/demarche because npm's
typosquat-similarity policy rejected the unscoped demarche name (too
close to an existing decache package). The Python package on PyPI is
the unscoped demarche.
Quickstart — generic OAuth OBO
from demarche import Verifier, OAuthOBOAdapter, JWKSKeyProvider
verifier = Verifier(adapters=[
OAuthOBOAdapter(
issuer="https://your-issuer.example/",
audience="https://your-api.example",
key_provider=JWKSKeyProvider(
"https://your-issuer.example/.well-known/jwks.json"
),
),
])
result = await verifier.verify(token)
result.principal_id # the user who delegated authority
result.agent_id # the agent acting on the user's behalf
result.scopes # what the agent is authorized to do
result.audit_id # opaque ID for log correlation
FastAPI integration
A complete worked example lives in
examples/fastapi-app/ — runnable smoke test
included.
from demarche.fastapi import DemarcheAuth
auth = DemarcheAuth(verifier)
@app.post("/book-meeting")
async def book_meeting(
agent: Annotated[
VerificationResult,
Depends(auth.require_scope("calendar.write")),
],
):
return {"booked_by": agent.principal_id, "via_agent": agent.agent_id}
Built-in HTTP error mapping:
| Failure | Status |
|---|---|
Missing / malformed Authorization header |
401 |
| Token issuer not recognised | 401 |
| Bad signature / expired / malformed token | 403 |
| Missing required scope | 403 |
Authentication is checked before scope — unauthenticated requests never
leak whether a given scope exists.
What Demarche is not
- Not an issuer. Demarche does not mint credentials. It validates
credentials issued by Entra, Auth0, OpenAgents, etc. - Not an agent framework. Bring your own runtime.
- Not a governance platform. Audit-log persistence and compliance
evidence bundles are out of scope for v0.x (planned for v0.5+).
Roadmap
| Version | Scope |
|---|---|
| v0.1 | Core verifier + OAuth OBO + Entra/Auth0 profiles + FastAPI integration |
| v0.2 | TypeScript SDK at parity; WorkOS profile |
| v0.3 | A2A Agent Card adapter (post-spec freeze); Express middleware |
| v0.5 | Optional hosted audit-log service; W3C VC adapter |
| v1.0 | Third-party security audit |
Threat model
See spec/v0.1-architecture.md §5.
Highlights: confused-deputy mitigation via separated principal_id and
agent_id; downgrade-attack mitigation via strict first-match adapter
resolution; key-rotation handling via JWKSKeyProvider with refresh
rate-limiting.
Packages
| Package | Language | Registry | Status |
|---|---|---|---|
demarche |
Python ≥3.10 | PyPI | Pre-alpha |
@deeplethe/demarche |
TypeScript ≥20 | npm | Placeholder (parity in v0.2) |
License
Apache 2.0. See LICENSE.