Connecting agents with OAuth
Spider's Customers service is also an OAuth 2.1 authorization server: a third-party agent (for
instance an MCP-based AI assistant such as Claude or ChatGPT) can ask a Spider user to authorize
it, and receive its own revocable credential scoped to that user - without ever seeing that user's
password.
This page is only about human-delegated access - a person authorizing an agent to act on their behalf, through a browser consent screen.
If you are building a script, a backend integration, a CI job, or anything else that is not a
person clicking "Approve", keep using a Service Account with the
client_credentials flow described earlier in this section. OAuth client registration and the
consent flow below do not apply to that case at all.
Turning it on
Two independent switches gate this feature, both off by default on every Spider deployment:
- A licence capability,
MCP- granted at TEAM tier and above. Without it, every OAuth endpoint (including the discovery document) answers403withcode: LIC-GATE-001andmissingAssess: "MCP", and the Connected apps tab described below is not shown at all in Settings - it isn't disabled, it's simply absent. See Licence tier below for how to tell this apart from a configuration mistake. customers.oauth.enabled- a deployment-level flag, independent of licensing. When it isfalse, every OAuth endpoint answers404, deliberately - the server is designed to look like it has no OAuth support at all, rather than reveal that the feature exists but is switched off.
Both must be true for the connection flow (registration, /authorize, the consent screen, /token,
/introspect, /revoke) to work. Turning on the licence alone, or the flag alone, is not enough
for that flow.
Three routes are the exception, gated on the MCP licence alone: the JWKS document and the
Connected apps backend (GET/DELETE on a user's own grants) never check
customers.oauth.enabled in code - only the licence gate applies to them. In practice this rarely
matters for the grants routes, since a deployment with the licence but the flag off has no way to
create a grant in the first place, so the grants list is simply always empty.
The eight client/callback-catalogue administration routes described below (Managing OAuth clients
and the callback catalogue) are not in that exception: they answer the same 404 as every
other OAuth endpoint while customers.oauth.enabled is false, for an authorised admin exactly as
for anyone else - the gate checks oauth.enabled before it even looks at rights, so there is no way
to tell "disabled deployment" from "not authorised" from either response. You cannot curate the
callback catalogue, or view/pause/delete a client, before turning customers.oauth.enabled on.
customers.oauth.clientIdMetadataDocuments (default true) lets an agent identify itself with a
Client ID Metadata Document instead of registering - see
Client ID Metadata Documents below. It makes Customers fetch
URLs from the internet. On an install without internet egress, set it to false: an agent that
sees the feature advertised uses it and does not fall back to registration when the fetch fails.
open, gated and disabled dynamic registration
Once the feature is on, customers.oauth.dynamicRegistration controls what a new agent must do
before POST /customer/v1/oauth/register will accept it:
open(the default) - any agent can self-register a client with no prior admin action. This is what makes "paste a URL, sign in, approve" a one-step experience for the end user. The trade-off: this is Spider's only unauthenticated, world-writable endpoint. It is rate-limited (customers.oauth.registrationRateLimit, 10 registrations per hour by default) - but that ceiling is platform-wide, not per caller, so a busy self-serve tenant registering many agents can exhaust it for everyone behind the same gateway. Raise the limit deliberately if that happens; it is a real trade-off against abuse resistance, not a bug to silently work around.gated- the registration endpoint stays open (it does not 404), but a registration is only accepted when everyredirect_uriit declares matches an entry an admin has enabled on the callback catalogue (see below). A non-matching registration is refused with the sameinvalid_redirect_uriresponse a structurally malformed URI would get - the caller learns its URI was unacceptable, never whether a catalogue exists or what is on it. This is the mode for a self-hosted operator who wants to control which agent products can connect, without an admin action per end user: enable claude.ai's catalogue entry once, and every Spider user who subsequently connects Claude self-registers and gets consent-screened exactly as inopenmode.disabled-POST /customer/v1/oauth/registeranswers404, the same way the rest of the surface does whencustomers.oauth.enabledis off. No new client can be registered by any means; use it if the authorization surface only ever needs to serve clients already registered some other way.
Registering a client is never an admin action, in any mode - there is no "create a client"
button or API route anywhere on this surface (see Managing OAuth clients, below, for what the
admin surface actually does). What gated restricts is which redirect URLs a self-registering
client may claim, via the catalogue.
The callback catalogue
The callback catalogue is the list of redirect-URL patterns an admin has decided Spider will trust. It does two things:
- In
gatedmode, it is the allowlist a registration must match to be accepted at all. - In every mode, a match against an enabled entry is what makes a freshly-registered client
verified(see Reading the consent screen, below) - the one piece of independent evidence Spider has that a client's declared redirect host is really operated by the vendor it claims to be.
The catalogue has two kinds of entry:
- Shipped vendor entries, updated with each Spider release as agent platforms change their callback URLs. Today Spider ships definitions for Claude (claude.ai), ChatGPT, and Claude Code (a native client, which redirects to a loopback address on an ephemeral local port rather than a hosted URL). Every shipped entry starts disabled - shipping a definition and trusting it are two different decisions, and the second is always yours.
- Custom entries, which you add yourself for a self-hosted or bespoke client no vendor catalogue could know about. A custom entry also starts disabled.
Authorising a vendor
- Click the OAuth clients icon in the left icon band (visible when your licence carries
MCP, OAuth is turned on for the deployment, and you hold the right described in Who can do this, below). A popover opens with a search box, the list of already-registered clients, and a Callback catalogue entry below the list. - Click Callback catalogue. This opens the catalogue directly - it does not require an existing client, so it works the same way on a brand-new install with zero registrations yet.
- Find the vendor (e.g. "Claude (claude.ai)") and enable it.
- From that point, a registration whose
redirect_urismatch that vendor's callback URL(s) is accepted ingatedmode, and is markedverifiedin every mode.
Disabling a vendor afterwards only stops future registrations from matching it (in gated
mode) or being marked verified (in every mode) - it never touches a client that already
registered while the entry was enabled. That client keeps its verified badge and keeps working;
disabling is not a way to retroactively distrust or cut off a client you have changed your mind
about (use Managing OAuth clients, below, for that).
Adding a custom callback
For a client no vendor catalogue covers - an in-house tool, a bespoke integration - add a custom
entry from the same Callback catalogue page: give it a name and one or more https:// callback
URLs (no fragment, no query-string userinfo; loopback URLs are not accepted for a custom entry). It
is created disabled, exactly like a shipped one; enable it the same way once you are ready for it
to be matched.
Managing OAuth clients and the callback catalogue
Who can do this
Reaching this admin surface at all requires:
- The
MCPlicence capability (TEAM tier and above). - Either Admin, or the
oauthClients.createright, granted per-user from the user's Rights tab (the same placeotelTargets.createlives). Holdingcreatelets you view registered clients and curate the callback catalogue (enable/disable a vendor, add or remove a custom entry, pause a client). - Deleting a client - the one action that also revokes its live grants - additionally requires
the separate
oauthClients.deleteright. The two checkboxes are not symmetric: granting someonecreatedoes not, by itself, let them destroy another team's client and its active credentials, butdeleteonly ever means anything alongsidecreate- the Rights tab's Delete checkbox is disabled unless Create is (effectively) ticked, and unticking Create clears an already-ticked Delete in the same save, so a user is never left holdingdeletewhile lackingcreate.
An unauthorised caller gets the same 404 not_found a nonexistent endpoint would - never a 403
that would confirm the feature exists but is closed to them.
Viewing and pausing clients
The popover behind the OAuth clients icon (see Authorising a vendor, above) lists every
client that has ever self-registered, searchable by name or id, each row showing a state dot
(active/disabled). Click a row to open that one client's detail view, whose Global tab shows
its verified badge, whether it is currently disabled, and how many grants it currently holds
live - one client per view, not the whole list. A client identified by a Client ID Metadata
Document carries a CIMD badge in the list and, on its Global tab, a "Client id (metadata
document)" field, a "JWKS URI" field when it declared one, and a "Metadata document refreshes
after" field showing when Spider next re-reads it.
Pausing a client (disabled: true, toggled from that Global tab) stops it from completing new
authorizations, issuing new tokens, or refreshing existing ones - but deliberately leaves every
grant it already holds untouched, so you can pause a client you are investigating without
destroying the evidence a live grant represents (who is using it, what it is doing) or
disconnecting users mid-investigation.
Deleting a client
Deleting a client is the destructive counterpart to pausing: it revokes every grant that client
still holds live, then removes the client registration itself. Use it once you are done
investigating and have decided the client should be gone, not merely paused. It requires the
oauthClients.delete right in addition to oauthClients.create (see Who can do this, above).
Client ID Metadata Documents (CIMD)
With dynamic registration, a hosted agent registers a new OAuth client every time a user connects
it, so the client list fills with duplicates of the same application. With a Client ID Metadata
Document (CIMD), the agent's client_id is an https:// URL where it publishes its own
description - name, redirect URIs, authentication method. Spider fetches that document the first time
the agent connects and keeps one client for it, however many users connect it and however often.
Spider advertises CIMD in its discovery document whenever customers.oauth.clientIdMetadataDocuments
is on and registration is not disabled. Claude Code and ChatGPT use it automatically.
What Spider checks before trusting a document:
- The URL is
https://on port 443, with a path, no fragment and no credentials in it. - The host resolves only to public addresses - never loopback, private, link-local or cloud-metadata ranges - and Spider connects to the address it checked.
- The response is JSON, at most 5 KB, served without a redirect, within 5 seconds.
- The document's own
client_idequals the URL it was fetched from, its redirect URIs arehttps://(or loopback for a native client), and it carries no secret.
Spider re-reads the document when its cache lifetime expires (between 5 minutes and 24 hours, from the
server's Cache-Control), and follows what it now says. If the document cannot be fetched - or the
fetched document is invalid - Spider keeps using the last good copy for up to 24 hours, then refuses
the agent until the document is reachable again.
In gated mode, a CIMD agent is accepted when its client_id is listed on an enabled callback
catalogue entry, or - as for registration - when all its redirect URIs match one. The shipped ChatGPT
and Claude Code entries already list their vendors' published metadata URLs; enable the entry and
those agents connect. A custom entry can list client metadata URLs too.
Pausing and deleting. A CIMD client can be paused like any other. Deleting one revokes its grants, but the next connection re-creates it from its document - to keep an agent out, pause it instead.
Native clients. Claude Code publishes http://localhost/callback and http://127.0.0.1/callback
and listens on a different port each run, so for a CIMD client Spider accepts those loopback redirects
on any port. Such a client is never marked verified: any program on the user's machine could listen on
that port.
Connecting an agent (open mode)
This is the common case for a hosted or demo-tier deployment:
- In the agent (e.g. Claude, ChatGPT, or any MCP-compatible client), paste Spider's MCP server URL.
- The agent discovers the authorization server automatically from
/.well-known/oauth-authorization-serverand registers itself - you do not do this step. - Your browser is sent to Spider's login page if you are not already signed in, then to a consent screen (see Reading the consent screen below).
- Click Approve. You are returned to the agent, which now holds its own access and refresh tokens - never your password, and never your own Spider session token.
Connecting an agent (gated mode)
- An administrator authorises the agent product once, per deployment - not per user - by enabling its entry on the callback catalogue (see Authorising a vendor, above), or by adding a custom entry if no vendor catalogue entry exists for it.
- From there, every end user connects the same way as in
openmode above: they paste the MCP server URL into the agent, the agent self-registers automatically (this is whatgatedmode changes - the registration only succeeds because its redirect URL now matches an enabled catalogue entry), and each user's own connection follows the same consent flow.
There is no client_id/client_secret for an administrator to generate or distribute out of band:
registration is never an admin action, in any mode (see open, gated and disabled dynamic
registration, above).
Reading the consent screen
The consent screen shows the connecting agent's declared name, the host it will redirect back to, and - since the callback catalogue shipped - a trust signal derived from that host:
client_nameis supplied by the client itself and is not verified in any way. An agent calling itself "Trusted Analytics Tool" is asserting that, not proving it.- The redirect host is the only field on this screen you can actually trust. It is derived server-side from the redirect URI the client registered, and Spider validates every redirect against that registered value before ever reaching this screen. If the host does not look like the agent you meant to connect, deny the request. This is now also the evidence the badge below is built from, which makes this sentence more true, not less.
- "Published by
<domain>" appears for an agent identified by a Client ID Metadata Document. That domain served the agent's description, so the agent's operator controls it - a stronger signal than the name, which the agent chooses. For Claude Code, which redirects to your own machine, it is the only hosted domain on the screen. - A green "Verified" badge appears when every one of the client's redirect URIs matched an
enabled callback-catalogue entry at the moment it registered. This is evidence, not a promise:
it means the redirect host really is the one an admin decided to trust for that vendor, and
nothing more. It is computed once, at registration - if an admin later disables that catalogue
entry, an already-registered client keeps its badge; the badge is not re-evaluated on every
consent screen.
- What the badge does not mean: it says nothing about what the client will do with the access you grant, nothing about the specific instance of the agent asking you (only the vendor's callback host), and nothing about a client that has since been compromised or repurposed while keeping the same, still-enabled, redirect host. Treat it as "this callback host is the one an admin trusted for this vendor", not as "this specific request is safe to approve without reading it".
- No badge does not mean untrustworthy. A self-hosted or in-house client an admin has not
(yet) added to the catalogue, or one connecting in
openmode against a vendor entry nobody has enabled, simply shows no badge - the same as a client whose redirect URI never matched anything. Absence of the badge is absence of that one piece of evidence, not a warning.
- A "loopback" notice appears instead of the badge when the redirect host is a literal loopback
address (
localhost,127.0.0.1,[::1]) - the case for a native client like Claude Code, which redirects to an ephemeral port on your own machine rather than a hosted URL. A loopback callback can be admitted by the catalogue (a native client has nowhere else to redirect to) but is neververified: any process running locally can bind that port and claim to be the legitimate client, so there is no vendor-controlled host to vouch for it. Judge a loopback connection by context - did you just start this native client yourself - not by a badge that structurally cannot appear for it.
Licence tier
Connecting agents requires the MCP licence capability, granted at TEAM tier and above -
a BASIC licence never carries it. An install that lacks it shows two symptoms together, which
is how you tell "not licensed" apart from "not configured":
- Every OAuth API call answers
403with a JSON body namingcode: "LIC-GATE-001"andmissingAssess: "MCP". - The Connected apps tab does not appear under Settings at all - not greyed out, not showing an empty state, simply not in the tab list.
If instead you get a 404 from the OAuth endpoints (or the discovery document is unreachable),
that is the separate customers.oauth.enabled flag - see Turning it on above.
Restricting who can resolve a token (customers.oauth.introspectionClients)
POST /customer/v1/oauth/introspect is how a resource server (an MCP server sitting in front of
Spider, for instance) turns an agent's opaque access token back into a live Spider identity. Calling
it requires either the MCP server's own application identity (see below) or a Service Account
credential - and by default, any Service Account on the deployment can call it and receive back
the token-granting user's full, privileged Spider JWT, not just the resource server it was meant for.
Spider's own MCP server does not use a Service Account for this: it authenticates with a
Config-issued application identity, which needs no long-lived secret to provision, leak or
rotate. Applications allowed to introspect are listed in customers.oauth.introspectionApplications
(default ["mcp"]); any other application is refused with the same 403 as a caller that is not a
Service Account. The allowlist below governs Service Accounts only.
Set oauth.introspectionClients in the customers Config document (the Helm chart does not expose it as a value) to the customer id(s) of the Service Account(s) that are
allowed to introspect - typically just the one backing your MCP/resource server. Leaving it empty
(the default) keeps today's behaviour: every Service Account may introspect. If you turn OAuth on
(customers.oauth.enabled: true) and leave this list empty, the service logs a startup warning
(CUSTR-OAUTH-084) naming the config key, so the open posture is visible rather than silent. A
Service Account that is not on a non-empty list gets the same rejection as one that isn't a Service
Account at all - there is no way to detect, from the outside, that an allowlist is even configured.
Revoking access
A connected agent's access can be ended from either side, and they are not the same as signing out of Spider:
- As the user, go to Settings > Connected apps, and revoke the entry for the agent. This ends the whole grant immediately - the agent's access and refresh tokens both stop working.
- As the client (an agent or its operator revoking its own credential), call
POST /customer/v1/oauth/revokewith the token to revoke.
Signing out of Spider in your browser does not revoke a connected agent. Every OAuth grant gets its own, independent Spider session at consent time, specifically so that closing a browser tab or logging out does not silently kill an agent's access out from under it with no warning. The Connected apps tab exists precisely to give you an explicit way to end that access when you do want to - browser logout is not that control.
How it works
Notes on this diagram:
- The consent step is not a browser redirect at either Login GUI hop - approve/deny are same-origin calls from the Login GUI, which then does the actual browser navigation itself.
- The token this exchange produces is opaque, not a JWT - it is meaningless to any other Spider service and only this authorization server's own introspection endpoint can resolve it back to an identity. That is what makes it safe to hand to a third-party agent in the first place.
See the full API reference in the OpenAPI documentation for every endpoint's
request/response shape, or OAuthAuthorizationServer.md at the monorepo root for the underlying
design rationale.