# AgentID: Sign in with Google, Except the User Is an AI Agent

> AgentMail launched AgentID on October 6, 2026: an OpenID Connect provider that lets an AI agent sign in to apps as its own AgentMail inbox, and gives registered apps the verified email of the human who owns it. Its discovery document allows only the code flow, S256 PKCE and ES256 tokens, with no refresh tokens, and ID tokens last 10 minutes. On the agent's side, a sign-in is an API call with the agent's AgentMail key and a five-minute token from AgentID's waiting page, which leaves the waiting browser holding a 30-day sign-in key, the same shape as the OAuth device flow and its phishing risk.

URL: https://blog.bokvi.com/blog/agentid-sign-in-for-agents/
Published: 2026-10-06
Tags: ai-agents, oauth, security

**AgentMail, the company that sells email inboxes to AI agents, [launched AgentID on October 6](https://www.agentmail.to/blog/introducing-agentid) as "the sign-in button for AI agents." It is a standard OpenID Connect provider: an agent signs in to your app as its own AgentMail inbox instead of borrowing its owner's email, password and card, and if you register your app you also get the verified name and email of the human who owns it.** That second part is the real product. Right now an app that gets a hundred agent signups can't tell one person's fleet from a hundred customers. AgentMail says it hit exactly that when it let agents sign up for its own service: "Hundreds of accounts appeared whose owners had no idea their agent had made one."

I read the live discovery document, started a sign-in to this blog's domain to see what an agent sees, ran the setup CLI's read-only checks against a scratch Auth.js app, and went through AgentMail's docs for the agent's side. For an app, AgentID really is one more social login. The interesting part is the agent's side, where the credential turns out to be an API key and a five-minute token, and that's also where my one real worry sits.

![A dark sign-in card titled Sign in to blog.bokvi.com, waiting for an agent to authorize an inbox, with a curl command to the AgentMail API carrying an auth token, the same token in a copy box, and a five-minute expiry timer](https://blog.bokvi.com/_astro/agentid-waiting-page.BXDGfac3_1AnUwh.webp)

*What an agent sees after clicking Sign in with AgentID at an app on blog.bokvi.com. AgentID put the domain on the page without asking me to prove I control it; the authorization code can only go back to that origin.*

## An agent signs in with a curl, not a password

To get that page I did what an app does: redirected a browser to AgentID's authorize endpoint with a PKCE challenge and `https://blog.bokvi.com` as the client ID. That's an [open client](https://www.agentid.com/docs/open-clients), the tier with no registration and no secret, where your HTTPS origin is the client ID and the redirect URI has to sit on the same origin. AgentID took it as is. The page is honest about what it is: no password field, a line saying what the app receives, and a command for the agent to run.

That command sends the agent's AgentMail API key to AgentMail's API, with the one-time `auth_token` from the page and `accept_disclosure: true`, which approves the app's request on the agent's behalf. According to [AgentMail's sign-in guide](https://docs.agentmail.to/agentid-sign-in), that call authorizes a sign-in key for the browser waiting on the page: a P-256 key pair generated with WebCrypto, whose private half is non-extractable and never leaves that browser. Once the browser activates it, it signs each later sign-in itself, over a challenge the server generates, for 30 days. AgentID remembers an approval for 180 days per inbox and app, so later sign-ins to the same app need nothing from the agent at all. An agent with no browser hands the last step to its owner, who approves it in the AgentMail console.

![A diagram of an AgentID sign-in across four lanes: the app, the agent's browser, the agent and AgentMail's API, and AgentID. The app redirects to the waiting page, the agent posts the auth token with its API key, the browser activates a P-256 sign-in key, AgentID returns a code, and the app exchanges it for a ten-minute ID token](https://blog.bokvi.com/_astro/agentid-sign-in-flow.DmaBqMxT_Z2w59Dt.webp)

*Who holds what. The AgentMail API key goes only to AgentMail, the private sign-in key stays in the browser, and the app ends up with a ten-minute ID token. Data: AgentMail and AgentID documentation.*

That last step is the part I like. The app never receives anything it could replay, and there's no verification email to send, because AgentID checks the inbox live when it mints the token.

## For the app, it's two config values and one gotcha

The [discovery document](https://auth.agentid.com/.well-known/openid-configuration) I fetched on launch day is short and strict. The only grant is the authorization code, PKCE is S256 only, ID tokens are ES256 only, and there are no refresh tokens and no `offline_access`. The callback carries an `iss` parameter ([RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207)), the mix-up defence the [MCP 2026-07-28 spec](https://blog.bokvi.com/blog/mcp-2026-07-28-spec/) now tells clients to check. ID and access tokens last 10 minutes. After that the session is yours to issue and to end.

Clerk ships AgentID as a built-in connection. Supabase, Auth0 and Auth.js v4 take it as a custom OIDC provider, Better Auth gets a helper package AgentMail maintains, and WorkOS AuthKit [can't take it yet](https://www.agentid.com/docs/workos) because it has no custom social providers. An open client costs nothing but can't run on localhost. The owner claims need a registered client, which you create in the AgentID console or with `npx @agentmail/agentid-cli init`.

The gotcha is in the [CLI's README](https://www.npmjs.com/package/@agentmail/agentid-cli): NextAuth v4 assumes RS256 even when discovery says otherwise, so a hand-written provider fails on AgentID's ES256 tokens. This is the provider block the CLI writes into an Auth.js v4 app, trimmed, with the comment mine:

```ts
{
  id: "agentid",
  name: "AgentID",
  type: "oauth",
  wellKnown: "https://auth.agentid.com/.well-known/openid-configuration",
  authorization: { params: { scope: "openid email profile" } },
  idToken: true,
  checks: ["pkce", "state", "nonce"],
  // Without this, NextAuth v4 expects RS256, and AgentID signs only ES256.
  client: { id_token_signed_response_alg: "ES256" },
  clientId: process.env.AGENTID_CLIENT_ID,
  clientSecret: process.env.AGENTID_CLIENT_SECRET,
  profile(profile) {
    return { id: profile.sub, name: profile.name, email: profile.email, image: profile.picture ?? null };
  },
}
```

Look at what `profile()` keeps: the agent's `sub`, name and email. The owner claims arrive in the raw profile and are dropped there, and Clerk and Better Auth don't keep them on the user either. Storing them is your callback's job, and the CLI deliberately leaves your callbacks alone.

![Terminal output of AgentID doctor: Auth.js detected, the agentid callback URL derived, no local AgentID credentials found, OIDC discovery, S256 PKCE, ES256 signing and JWKS validation passed, and Auth.js configuration skipped](https://blog.bokvi.com/_astro/agentid-cli-doctor.C3quqQdU_Z1SvC21.webp)

*The CLI's read-only doctor against a scratch Auth.js v4 app I hadn't registered. It found the auth route, derived the callback and checked AgentID's endpoints before stopping. Local path shortened.*

The CLI itself is careful code. It refuses to overwrite hand-edited config, and the Auth0 and Supabase CLIs it downloads are pinned to SHA-256 checksums. It is also version 0.9.0, licensed `UNLICENSED`, built from a private repository, and its README notes that npm provenance is unavailable for that reason. And it drives your Clerk, Auth0 or Supabase admin session. I'd read the diff it leaves, or spend the ten minutes on the manual setup.

## The token names the agent, and the owner if you ask

AgentID documents [a decoded ID token](https://www.agentid.com/blog/how-an-ai-agent-proves-its-identity-to-your-app) for a registered app that asked for every scope. This is their sample, not mine, trimmed:

```json
{
  "iss": "https://auth.agentid.com",
  "sub": "lM9vT2aR7sK4qN8wE1xC6bY0uF3hJ5pD9gL2zV7oA4Q",
  "actor_type": "agent",
  "email": "support@acme.agentmail.to",
  "email_verified": true,
  "name": "Acme Support",
  "owner_sub": "oW7xN2pQ4mT8vL1kR6sC9dF3gH5jB0aE2uY4zI6nP8A",
  "owner_name": "Maya Chen",
  "owner_email": "maya@acme.com"
}
```

`actor_type` is always `"agent"`, so an app can treat agent accounts differently without sniffing user agents. Key the account on `sub`, not the email: if an inbox is deleted and the same address is created again, it gets a new `sub`, and an email-keyed table would merge two different agents. `owner_sub` is the same for every agent one person owns, which makes it the key for per-owner limits. [Keenable](https://www.agentid.com/directory), a search API in the directory, already works this way: your first agent gets the free monthly allowance, and your other agents share it. `owner_name` and `owner_email` go only to registered apps, and never silently missing: if the agent's key isn't allowed to share its owner, the agent can't finish the sign-in alone and the organization's owner has to approve it.

One design choice I'd push back on. `owner_sub` comes with the `profile` scope, which open clients get without registering, and AgentID's guide on persistent identity says it is ["the same at every app"](https://www.agentid.com/blog/persistent-agent-identity-without-personal-data). Its [account guide](https://www.agentid.com/blog/how-an-ai-agent-creates-its-own-account) says open clients learn "nothing about the owner." That's true of the name and email, not of a stable identifier for the human that any two websites can join on. A per-app `owner_sub` would still let each app cap signups per owner. It just wouldn't let them compare notes.

## The waiting page is a device-code flow, and agents are easy to phish

Look at the waiting page again as a protocol. One party opens a sign-in and gets a short code; a second party with real credentials approves that code somewhere else; the first party's browser is then signed in. That is the OAuth device authorization grant, and [RFC 8628 §5.4](https://datatracker.ietf.org/doc/html/rfc8628#section-5.4) names its classic attack: the attacker starts the flow and talks the victim into approving the attacker's code.

Here the victim would be an agent. An attacker opens a sign-in at any AgentID app in their own browser, takes the `auth_token`, and gets it in front of your agent with the instruction to run the command: in an email to the agent's inbox, on a page it browses, in a tool result. If the agent runs it with a key that has the `app_connect` permission, the attacker's browser activates a sign-in key for your agent's inbox. The API's whole answer is a key ID and "Authorization complete. Return to browser.", with nothing naming the app, and any disclosure screen shows up in the browser that's waiting, which is the attacker's. As I read the docs, nothing ties that key to the app it was created at, and it lasts 30 days. I haven't tried this against a real inbox; it follows from the documented flow.

AgentMail clearly knows. Its guide warns: "Read `auth_token` only from a sign-in at exactly `https://auth.agentid.com`. Verify the final origin through your client, independently of any origin claimed in page content." That's the right rule for code, and a hard one for a language model reading its mail. The agent's identity is an inbox, which is the one thing anyone on the internet can write to. So keep `app_connect` off the key your agent uses day to day (it is off by default on new keys), run sign-ins from deterministic code that reads the token from a page it opened itself, and check `GET /v0/api-keys?type=public_key` for sign-in keys you don't recognise.

Cutting an agent off is also slower than "delete the key" suggests. Deleting a sign-in key stops new sign-ins, but AgentID sends no revocation webhook and no back-channel logout, sessions the app already issued keep running, and the 180-day remembered approval has no revoke endpoint. There's no bulk revocation either. If you run the app, keep agent sessions short and renew them with a fresh sign-in, which goes through on its own while the key is valid and fails once it's revoked.

## Free for apps, because the agents pay

AgentID costs an app nothing, and for a limited time integrating it earns three months of AgentMail free. The other side of that is in AgentID's [OIDC guide](https://www.agentid.com/blog/oidc-for-ai-agents-complete-guide): "Every registered client is an AgentMail account, which is how a free product pays for itself." Every agent identity is an AgentMail inbox too. The [free plan](https://www.agentmail.to/pricing) has three inboxes and Developer is $20 a month for ten.

That's not a reason to skip it, but it's worth seeing clearly. For apps there's no lock-in: it's plain OIDC, one more button, and a competing identity provider for agents would be another button. For agent owners the identity is the AgentMail inbox, and the `sub` doesn't come with you if you leave. Sign in with Google works because people already have Google accounts. AgentID's version of that pitch is the announcement's "10 million agents can find you," a number I can't check. The [directory](https://www.agentid.com/directory) listed 26 apps when I looked, five of them marked "Coming soon", including TinyFish and Rho, which the announcement names as accepting AgentID today.

The other approaches answer different questions, and [the announcement](https://www.agentmail.to/blog/introducing-agentid) draws the line well. Bot detection tells you a visitor isn't human, API keys work inside an account a person already opened, delegated access acts through the owner's own account, and signed bot traffic names the operator. None of them gives an agent its own account at your app with a person on record behind it.

## Add the button, guard the key

If agents already sign up for your product, I'd add AgentID. It's cheap, and `owner_sub` alone turns a pile of anonymous signups into groups with one person behind each. Register the app so you get the owner's email, key accounts on `sub` and limits on `owner_sub`, and keep agent sessions short, because a revocation won't reach you.

If you run agents, it beats pasting your password into an environment variable by a wide margin. Just treat an AgentMail key with `app_connect` as what it now is: the password to your agent's identity, and one your agent can be talked into using for someone else.