Authdog is the identity provider. The framework keeps its own auth hook. Register an agent, mint a short-lived bearer with client_credentials, and verify that bearer at the hook before the agent, tool, or graph runs.
Each hook is grouped under AI. The group has pages for the auth boundary, machine credentials, tool checks, and what you put in the prompt.
Runnable copies live in authdog/agents-samples. Copy one folder. Each folder is its own project.
Which path
| You need | Open |
|---|---|
| An agent signs into your app through the browser | Agent Sign-In |
| An agent calls your API, an MCP server, or another agent | The framework pages below |
| An MCP client signs a person in through hosted OAuth | MCP authentication |
| An agent acts as a person | Acting for a person |
Where the hook is
| Framework | Hook | Role in the sample |
|---|---|---|
| A2A | Agent card security scheme, then require_auth on the JSON-RPC mount |
Server admits the caller. The client sends the bearer on HTTP. |
| Eve | Channel auth walk: oidc({ issuer, audiences }) |
Session routes verify the bearer JWT. |
| MCP | token_verifier on the MCP server |
whoami runs only after userinfo accepts the token. |
| LangGraph | require_auth on POST /invoke |
The graph runs only after that dependency returns a user. |
| OpenAI Agents SDK | Tool RunContext plus require_auth on the API the tool calls |
The tool presents the token. The route decides. |
| CrewAI | EXECUTION_START raises HookAborted |
The crew does not start when userinfo rejects the bearer. Sample planned. |
| Google ADK | before_agent_callback |
Returning content skips the agent. Sample planned. |
| Pydantic AI | RunContext deps, then require_auth on your API |
The tool presents the token. The route decides. Sample planned. |
| Claude Agent SDK | PreToolUse returns permissionDecision: deny |
The hook blocks the tool. Sample planned. |
| Mastra | server.auth.authenticateToken |
API routes require a userinfo-accepted bearer. Sample planned. |
| LlamaIndex Workflows | require_auth on the route that calls workflow.run |
The first step stops when the subject is missing. Sample planned. |
| Semantic Kernel | FUNCTION_INVOCATION filter skips next |
The kernel function does not run. Sample planned. |
| Vercel AI SDK | toolsContext on the tool, then require_auth on your API |
The tool presents the token. The route decides. Sample planned. |
| Strands Agents | BeforeToolCallEvent.cancel_tool |
A cancelled call does not run the tool. Sample planned. |
| LangChain | wrap_tool_call does not call the handler |
The middleware stops the tool. Sample planned. |
Rows marked "Sample planned" describe the hook. The folder is not in the repository yet, and the page does not pin a package version. subject_if_accepted and subjectIfAccepted in those snippets are the sample's userinfo helper. They are not framework APIs. They return the subject, or nothing when the bearer is missing or rejected.
Register the caller
In the console, open Agents → Create new agent identity. Copy the client id and client secret. The secret is shown once.
The protected process takes the environment public key (pk_...) from Project settings. That key selects the identity host. It does not mint tokens.
Keep the client secret in the process that calls the token endpoint. Put the access token on the HTTP request. Leave both out of prompts, graph state, and logs.
The Agents module is sales-gated. Until access is approved, an M2M application issues the same client_credentials grant. Keep a development token off your production API.
Mint the bearer
Read token_endpoint from the environment discovery document. Authdog's path is /oauth2/token.
curl -X POST "$TOKEN_ENDPOINT" \
-u "$AUTHDOG_CLIENT_ID:$AUTHDOG_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials"The response carries access_token, token_type (Bearer), and expires_in. There is no refresh token. Repeat the exchange before expiry.
Send it as Authorization: Bearer <access_token>.
What verification returns
require_auth and fetch_user_data both check the bearer with the environment userinfo endpoint. The public key chooses that host. A missing bearer, a userinfo rejection, or a payload with no subject is 401. An accepted payload looks like the samples' stand-in:
{"meta": {"code": 200}, "user": {"id": "agent_1"}}The samples read user.id, then user.sub, and return that string. Where the framework has a scope field, they record agent:invoke.
| Check | Result |
|---|---|
| Missing or rejected bearer | 401 |
| Accepted userinfo | Subject from id, then sub |
| Scope field, when the framework has one | agent:invoke |
| Refresh token | None. Exchange again before expires_in. |
pk_... |
Selects the identity host. Stays on the resource server. |
Eve is the exception on this page: it verifies the JWT with discovery instead of calling userinfo. The subject still has to be the one in that token. See Eve checks the JWT.
After the subject is known
The samples stop once the subject is known. A 200 from whoami means userinfo accepted the bearer. It does not mean the agent may call every tool.
The Agents registry is the next check on that subject:
| Control | Effect |
|---|---|
| Allowed scopes and tools | Globs on the trust-store row. * allows every tool. |
| Trust-store enforce | The MCP trust-store sample applies that row. The other samples stop at the subject. |
| Revoke | New tokens fail when the subject is the OIDC client id. Tokens already issued last until expires_in unless the resource server enforces the trust store. |
Apply authorization after the identity check when the API is yours.
Acting for a person
The framework hook stays the same. Replace the client_credentials access token with an RFC 8693 token. sub stays the user. act names the agent.
{
"sub": "<user id>",
"act": { "sub": "client:<agent client_id>" }
}Put that token in the Authorization header, or in the OpenAI Agents `Caller.token`. Grant token exchange on the agent in Agents. The delegation sample reads a token whose subject is the person. The other samples mint a token whose subject is the agent client.
One token, three callers
Mint once. Present that bearer to each userinfo gate. Run one sample at a time, or bind the ports below so they do not share 8000.
TOKEN=$(curl -s -X POST "$TOKEN_ENDPOINT" \
-u "$AUTHDOG_CLIENT_ID:$AUTHDOG_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
| python -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')LangGraph, from langgraph/python with PK_AUTHDOG set:
uvicorn server:create_app --factory --port 8001
curl -s -X POST http://127.0.0.1:8001/invoke \
-H "Authorization: Bearer $TOKEN"The JSON reply is {"reply":"subject:<id>"}. The same call without the header is 401 and the body has no subject:.
MCP, from mcp/python with PK_AUTHDOG set. The verifier reads this header. POST /mcp without it is 401. The sample's whoami tool returns the subject:
uvicorn server:create_app --factory --port 8002
curl -s -D - -o /dev/null -X POST http://127.0.0.1:8002/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'OpenAI Agents, from openai-agents/python. The route is the gate. The tool only attaches the header. OPENAI_API_KEY is required for Runner.run. pytest does not need it.
uvicorn tool_api:create_app --factory --port 8003
export WHOAMI_URL=http://127.0.0.1:8003/whoami
export AUTHDOG_ACCESS_TOKEN="$TOKEN"context=Caller(token=os.environ["AUTHDOG_ACCESS_TOKEN"])GET /whoami with the bearer returns {"subject":"<id>"}.
Eve checks the JWT
Eve does not call userinfo. oidc() verifies the bearer as a JWT against the issuer discovery document: signature, issuer, expiry, and audience. Agent access tokens for the environment are JWTs. Set AUTHDOG_ISSUER to the token's iss and AUTHDOG_AUDIENCE to its aud. The Eve process reads neither the client secret nor PK_AUTHDOG.
npx eve dev listens on port 2000 unless PORT is set.
curl -s -D - -o /dev/null -X POST http://127.0.0.1:2000/eve/v1/session \
-H "Authorization: Bearer $TOKEN"A session request with no bearer is 401. The health route stays public.
Python gate
A2A, MCP, LangGraph, and the OpenAI Agents sample import require_auth or fetch_user_data from the Python SDK in authdog/web-sdk. That package is installed from commit afad543932541a9e9d3c891421151509d04b49b2 in each sample's requirements.txt. pip install authdog-fastapi on the public index is a different name and is not the install those samples use.
Eve verifies the JWT with Eve's own oidc() helper and the issuer discovery document.
Related
| Read | To learn how to |
|---|---|
| Machine identity | Choose M2M, Agents, a service account, or a PAT |
| Agents quickstart | Register an agent and mint the first token |
| MCP authentication | Let an MCP client sign in through the hosted issuer |
| Agent delegation | Keep the user as sub when an agent acts for them |