Skip to main content

How Agents Authenticate to Willow

Every call to the Willow gateway settles one question first: which Willow identity is making this call? From there everything follows — the identity's groups decide which tools exist, guards evaluate against it, and the audit log attributes the call to it.

All the options below answer that one question. They differ in who vouches for the answer: your identity provider, you, or the user themselves.

This page covers the front door only. Which credential Willow then sends to the upstream service is a separate choice, in Authentication: Connector vs Proxy Modes.

The options

OptionWho vouches for the identityUse it when
Auth exchangeYour identity providerThe user is already signed in to your system through Okta or Keycloak, and you hold their token.
ImpersonationYou doYou know the user's email, and Willow can trust the calling client like an admin.
OAuthThe user, by signing inYou have to ask the user to sign in, and the client can store what it gets back.
OTTThe user, by signing inYou have to ask the user to sign in, and the client can't store an access token.
Machine users and background agentsNobody — there is no userThe caller acts as itself, not on behalf of a person.

Auth exchange

If your own product already authenticated the user against Okta or Keycloak, you're holding a JWT that proves who they are. Send it to the gateway with x-auth-exchange: true and Willow verifies it against your JWKS endpoint and acts as the matching Willow user.

This is the right answer for an internal system with an agent in it. The user signed in once, to you, and there is no reason to make them do it again. You also store nothing: no token to keep, no refresh to manage.

Security: the JWT decides who the call acts as; the API token you send alongside it only authorizes your application to perform exchanges at all. Both are required, and that API token is an organization credential that must stay on your server. Users must already exist in Willow — auth exchange resolves an existing user by email and never creates one.

Read more: Auth Exchange


Impersonation

When you know who the user is but have no token proving it, you can send an API token carrying the run:impersonate scope plus an x-user-email header, and Willow acts as that user. There is no proof — you assert the identity and Willow believes you.

That trade is the whole story: you are asking Willow to trust the calling client the way it trusts an admin. It's a reasonable ask for a service you run on infrastructure you control. It is not something to extend to anything running on an end user's machine.

Security: the token can act as any user in the organization, including admins, so a leak is equivalent to every account at once. Whatever code decides the value of x-user-email has become an authentication system — a spoofing or injection bug there is account takeover. Keep the token server-side, grant it only run:impersonate (never all, which implies it — as do legacy tokens created before scopes existed), and use a separate token per application, since revocation is all-or-nothing.

Where you can get an IdP token for the same user, auth exchange gives you the same experience with the identity proven instead of asserted. Prefer it.

Read more: API Tokens


OAuth

If nobody has signed the user in yet, ask them to. Willow runs an OAuth 2.0 / OpenID Connect authorization server: the client registers itself, sends the user to /authorize, and exchanges the resulting code for an access token. The user signs in through your normal identity provider, so the identity that comes out is the same one they get everywhere else.

This is the default for a human at an MCP client. Cursor, VS Code, Claude, and ChatGPT all implement the MCP OAuth spec and will handle the whole flow once you give them the gateway URL. Tokens expire and refresh, nothing long-lived sits in a config file, and the sign-in inherits whatever your IdP enforces — MFA, device posture, conditional access.

What OAuth asks of the client is durability: it has to keep its registration, store the access and refresh tokens, and host somewhere for the redirect to land. Established clients do all of this. If yours can't, use OTT.

Security: dynamic client registration is open by design, so any client that can reach the Connect host can register itself — turn on Allow only authenticated clients in security settings to limit this to clients you've configured. Require PKCE (S256): loopback redirect URIs match on scheme, host, and path only, so any local port is accepted and PKCE is what makes an intercepted code useless. Token lifetime defaults to 24 hours and is configurable.

Read more: OAuth Authorization Server


OTT

OTT gets you the same real sign-in as OAuth without the client having to hold on to anything. Your code invents its own token, asks the gateway for a sign-in URL carrying it, and gets that URL to the user however you like — a Slack DM, an email, a link in your own UI. The user signs in through SSO, which binds the token to their Willow account, and your code polls until that lands. From then on every call carries Authorization: <userAccessKey>:<OTT>.

The difference from OAuth is where the credential comes from. Because your code proposes the token itself, there's nothing server-issued to persist: no client registration, no refresh token, no redirect endpoint to host. A client with no database can hold the pair in memory and simply re-run the flow after a restart. That makes OTT the option for anything too thin to be a proper OAuth client — a chat bot, a CLI, an embedded runtime — and for clients that don't implement OAuth at all.

The identity is a genuine signed-in user, identical to the OAuth path. What you skip is the bookkeeping, not the sign-in.

Security: despite the name the OTT is not one-time — it's a reusable session token, 24 hours by default, after which the user signs in again. More importantly, the token binds to whoever completes the sign-in, not to the person you meant to send it to. Deliver the link privately to exactly one person; a link posted somewhere shared means the first person to click it becomes the identity your code acts as from then on.

Read more: User Session Authentication


Machine users and background agents

Both are non-human identities that authenticate the same way, with Authorization: Bearer <access_key>:<secret>. Neither acts on behalf of a person; each acts as itself. The difference isn't the authentication — it's how you give it tools.

Machine userBackground agent
You areintegrating a platform you already runbuilding an agent you want Willow to define
Tools come fromthe groups you add it to, like a personcapabilities granted to that one agent
Willow also managesnothing elsesystem prompt, skills, triggers, sessions, deployment to a runtime

So: a CI job, a cron script, or your own backend calling tools is a machine user. Something you want Willow to hold the definition of — prompt, scoped tools, triggers, session history — is a background agent.

Security: the secret is static with no expiry, so rotation is something you schedule rather than something the protocol does. Because there's no person behind it, everyone who interacts with it shares its access, so scope it to the least any of them should have. And it sits outside SSO entirely: no MFA, no conditional access, and no offboarding through your IdP — deprovisioning happens in Willow or not at all. For background agents, remember that deploying rotates the secret as a side effect, so any copy you were holding stops working.

Read more: Machine Users · Background Agents · Programmatic Gateway Access


Worked example

Authenticate a Slack Agent runs a single Slack bot through these options end to end: resolving the Slack user who triggered it to an email address, then impersonating them, asking them to sign in with an OTT, or running as a background agent instead — with the requests for each.