Repository

demarche

Vendor-neutral identity verification for AI agents

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

CI
PyPI
npm
License: Apache 2.0

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.