Skip to main content

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.

Use Service Accounts for machine-to-machine integrations

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:

  1. A licence capability, MCP - granted at TEAM tier and above. Without it, every OAuth endpoint (including the discovery document) answers 403 with code: LIC-GATE-001 and missingAssess: "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.
  2. customers.oauth.enabled - a deployment-level flag, independent of licensing. When it is false, every OAuth endpoint answers 404, 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 every redirect_uri it declares matches an entry an admin has enabled on the callback catalogue (see below). A non-matching registration is refused with the same invalid_redirect_uri response 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 in open mode.
  • disabled - POST /customer/v1/oauth/register answers 404, the same way the rest of the surface does when customers.oauth.enabled is 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 gated mode, 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​

  1. 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.
  2. 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.
  3. Find the vendor (e.g. "Claude (claude.ai)") and enable it.
  4. From that point, a registration whose redirect_uris match that vendor's callback URL(s) is accepted in gated mode, and is marked verified in 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 MCP licence capability (TEAM tier and above).
  • Either Admin, or the oauthClients.create right, granted per-user from the user's Rights tab (the same place otelTargets.create lives). Holding create lets 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.delete right. The two checkboxes are not symmetric: granting someone create does not, by itself, let them destroy another team's client and its active credentials, but delete only ever means anything alongside create - 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 holding delete while lacking create.

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_id equals the URL it was fetched from, its redirect URIs are https:// (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:

  1. In the agent (e.g. Claude, ChatGPT, or any MCP-compatible client), paste Spider's MCP server URL.
  2. The agent discovers the authorization server automatically from /.well-known/oauth-authorization-server and registers itself - you do not do this step.
  3. 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).
  4. 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)​

  1. 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.
  2. From there, every end user connects the same way as in open mode above: they paste the MCP server URL into the agent, the agent self-registers automatically (this is what gated mode 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).

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_name is 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 open mode 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 never verified: 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 403 with a JSON body naming code: "LIC-GATE-001" and missingAssess: "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/revoke with 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.