How to Let an AI Agent Sign In to Your Website
AI agent login for your website: add AgentID, the OpenID Connect provider built for agents, to the login you already have. Two configuration values, no SDK, and the agent signs in from a browser it enrolled once. Where sub, email, and owner_email actually arrive.
- AgentID
- ai-agent-login
- oidc

TL;DR
- No SDK, just a provider entry. AgentID is standard OpenID Connect. If your login accepts Google, it can accept AgentID with a configuration change.
- The agent signs in on its own. An enrolled browser holds the agent's key and completes the flow without a human. Agents with no browser hand the sign-in to their owner.
- You get identity and accountability in the token.
subandemailarrive in the id_token. Registered apps that request theowner_emailscope receive it in the id_token and from/v0/userinfo; open clients never receive it. - Fastest path:
npx @agentmail/agentid-cli initin your application's directory detects your auth provider and configures it.
To let an AI agent sign in to your website, add an OpenID Connect provider built for agents to the login you already have.
With AgentID, the sign-in for AI agents from AgentMail, that is two configuration values in your auth platform and no SDK:
- Issuer:
https://auth.agentid.com - Client ID: a
client_id
The agent completes the sign-in from a browser it enrolled once. Your callback exchanges the code exactly as it would for Google or GitHub, and the id_token gives you a stable subject and the agent's verified email address.
If you need to know which human owns the agent, create an AgentID application, request the owner_email scope, and read it from the id_token or the /userinfo endpoint.
The whole setup takes minutes on Clerk, Supabase, Auth0, Better Auth, or Auth.js, and it is free.
How does authentication for AI agents differ from a normal login?
A normal login assumes a person. An AI agent cannot complete a person's login steps as itself, so today it completes them as its owner.
Your login assumes a person in three places:
- OTP: it sends a code to a phone or an inbox the person can read.
- Password: it asks for a password the person remembers.
- Consent: it shows a consent screen the person clicks.
So the agent signs up with the owner's email, reads the owner's OTP, and clicks Approve in the owner's session.
From your side, the result is indistinguishable from the owner signing up. That is the problem:
- You cannot tell agents from people.
- You cannot count how many agents one person is running.
- You cannot cut off the agent without cutting off the person.
AgentMail hit this from the other direction. After it shipped an API endpoint that let agents sign up directly, it started emailing new users to ask how they had heard about the product.
Many of them did not know they had signed up at all, because their agent had done it. Most support requests began arriving from agents.
One large enterprise saw hundreds of signups from AgentMail-domain addresses in a short window. It had no way to tell whether that was one person's agent fleet or hundreds of customers.
An AI agent login fixes this by giving the agent an identity of its own that your app can verify, and a way to reach the human behind it.
What does AgentID return when an agent signs in?
When an agent signs in, AgentID returns an ES256-signed id_token carrying the agent's stable subject and verified email address. Registered apps that request the owner scopes also receive owner_email and owner_name, in the id_token and from /v0/userinfo; open clients never receive them.
AgentID is an OpenID Connect provider. The agent's identity is an email address on AgentMail, so every sign-in gives you something you can already handle.
The id_token, signed with ES256, carries these claims:
| Claim | What it is | Where it arrives |
|---|---|---|
| sub | Stable subject derived from the agent's inbox. Same agent, same sub, every sign-in. | id_token |
| email, email_verified | The agent's own inbox address, verified live when the token is minted. email_verified is always true when present. | id_token (email scope) |
| name | The agent's display name. | id_token (profile scope, registered clients) |
| scope | What this sign-in granted, so you can check rather than assume. | id_token (registered clients) |
| owner_email, owner_name | The human who owns the organization behind the agent. | id_token and /userinfo (owner scopes, registered clients) |
| iss, aud, exp, iat, jti, nonce | Standard OIDC claims. jti names this one sign-in. | id_token |
Each claim has a job:
subis what you key your agent accounts on.emailis how you reach the agent. Receipts, confirmations, and support replies all land in an inbox the agent reads.- The owner claims let you enforce a per-human limit or contact a person when something goes wrong.
How do you add auth for AI agents to your existing platform?
Add AgentID through your auth platform's custom OIDC provider setting. Every supported platform has one.
You give it the issuer, a client_id, and optionally a client_secret and the scopes to request. It does discovery from https://auth.agentid.com/.well-known/openid-configuration to find the endpoints and keys.
The per-platform guides walk each dashboard step by step:
The details that trip people up:
Supabase. The provider identifier carries a fixed custom: prefix, so you type only agentid and your code calls signInWithOAuth({ provider: 'custom:agentid' }).
Its Scopes field is comma-separated and defaults to nothing, which falls back to openid and email. Add profile there. Free projects are capped at three custom providers.
Better Auth. It uses AgentMail's helper package @agentmail/agentid-better-auth on top of the Generic OAuth plugin (Better Auth 1.7.2 or newer).
The helper fixes the provider id to agentid, so your redirect URI is your origin plus Better Auth's callback path ending in /agentid.
Auth0. Auth0 ships AgentID as a Marketplace social connection. It appears in the connections list rather than needing a hand-built enterprise connection.
Any other stack. The custom OIDC guide covers what a generic connector form will ask for, and which of its defaults will not work against this issuer:
- No refresh token, because
authorization_codeis the only grant. - No
offline_accessscope. - No end-session endpoint.
Should you set up AgentID from the dashboard or the CLI?
For most projects, set up AgentID with the CLI. Run this in the directory of the application you want to configure:
Bash
npx @agentmail/agentid-cli init
The CLI does four things:
- Detects Clerk, Auth0, Supabase, Better Auth, or Auth.js v4 in the project, or takes your callback URLs directly for any other OIDC stack.
- Registers a client. This opens a browser for your organization to approve; there is no unattended bypass.
- Writes the provider configuration.
- Verifies the result.
At the scope picker, press Enter for standard agent identity (openid, email, profile), or pass --owner-email to also request the owner's address.
npx @agentmail/agentid-cli doctor checks an existing setup without changing anything. It exits non-zero on failure, which makes it usable in CI.
The CLI configures your application to accept agents. It does not log an agent in; that side is covered next.
How does the agent actually complete the sign-in?
The agent completes the sign-in from a browser it enrolled once. After enrollment, it signs in on its own with a fresh signature each time.
Enrollment takes three steps:
- The agent posts an AgentMail API key to the enrollment endpoint.
- JavaScript on auth.agentid.com generates a keypair inside that browser.
- The browser submits the public key to AgentID.
The private key is a non-extractable WebCrypto P-256 key stored in IndexedDB under auth.agentid.com. In AgentMail's words: "Your private key is generated and stored in your browser and cannot be exported through browser JavaScript APIs."
A headless browser works fine.
After that, when your app redirects the agent to /v0/authorize, the enrolled browser recognizes the session. It signs a fresh, one-time signature over a server-generated transaction.
A disclosure screen shows which app is asking and what it will receive: the agent's inbox and name, and the owner's email and name if you requested them. Sign-ins that start from the Apps page in the AgentMail console skip that screen.
No human is involved when the agent has a browser.
If the agent has no browser, its owner completes the sign-in instead:
- The owner starts the sign-in from your app in their own browser.
- They choose Approve manually.
- They authenticate in the AgentMail console.
- They pick the agent's inbox and complete the approval there.
It has to finish in that same browser; there is no code to carry to another session. Either way, your callback receives an ordinary authorization code.
What does the flow look like on the wire?
On the wire, an AgentID sign-in is the standard OpenID Connect authorization code flow: an authorization request, then a code exchange.
If you implement OIDC yourself rather than through a platform, the two requests are the standard ones. The authorization request must use PKCE with S256 for open clients. Registered clients may use PKCE or an OIDC nonce.
Code
GET https://auth.agentid.com/v0/authorize
?response_type=code
&client_id=https://yourapp.com
&redirect_uri=https://yourapp.com/callback
&scope=openid%20email
&state=<opaque>
&code_challenge=<base64url(sha256(verifier))>
&code_challenge_method=S256
Your callback exchanges the code:
Code
POST https://auth.agentid.com/v0/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<code>
&redirect_uri=https://yourapp.com/callback
&client_id=https://yourapp.com
&code_verifier=<verifier>
Verify the id_token against https://auth.agentid.com/v0/jwks.json with whichever JOSE library you already use. Check iss and aud as you would for any provider.
Keys rotate with stable key ids and an overlap window, so caching by kid is safe.
How do you find out which human owns the agent?
To find out which human owns the agent, create an AgentID application, request the owner_email scope, and read it from the id_token or /v0/userinfo.
Click Create application in the AgentID console, or run npx @agentmail/agentid-cli init. That gives you:
- A client_secret, shown once (you can regenerate it).
- Up to 10 redirect URIs.
- Access to the owner scopes:
owner_profileandowner_email, which open clients cannot request.
Then request owner_email at /v0/authorize and exchange the code as above. The id_token carries owner_email, and /userinfo returns the same value:
Code
GET https://auth.agentid.com/v0/userinfo
Authorization: Bearer <access_token>
The response includes owner_email and, with owner_profile, owner_name.
The owner's email is personal data, so don't log tokens that carry it.
If the agent is not allowed to share its owner, it can't finish the sign-in alone; the organization owner has to approve it. You never get a token with the claim missing, so you can rely on the field whenever a sign-in succeeds.
The mechanics and the policy questions around this are in how to verify the human behind an AI agent.
What makes an AI agent login secure?
An AI agent login with AgentID is safer than the status quo, where agents sign in as people, because nothing reusable crosses the sign-in.
- The agent's private key stays in its browser.
- AgentID's own signing key is non-exportable.
- Every sign-in is a fresh signature that your app verifies against published keys.
Your app never receives a password or an API key. Revoking a credential stops that one agent; it touches nothing belonging to the owner and nothing belonging to other agents.
What the token does not tell you is whether the agent will behave. It proves which agent this is, not that it is well-intentioned.
The accountability comes from the owner claim. An agent whose human you can name is an agent you can allow, limit, or ban with a real address to write to.
Most of the relying-party decisions that follow, such as how many agents one owner may run or whether to gate certain features to agents with a verified owner, are covered in can an AI agent sign up for a SaaS.
What should your app do after an agent's first sign-in?
After an agent's first sign-in, key its record on sub, store the owner if you requested it, and send your normal onboarding email.
- Key records on
sub, not onemail. The address is stable too, butsubis the identifier the protocol guarantees. - Store
owner_emailalongside it if you requested it, and index on it so per-owner limits are one query. - Send your normal onboarding email to the agent's address, because the agent will read it.
- Add your app to the AgentID directory so agents browsing for services that accept them can find yours.
The button guide covers the markup and the brand rules for the login page itself.



