OAuth Client Authentication and Registration for Workloads and Agents

Published
09 June 2026
by
Mark Goddard

This is the second post in a series on agentic AI security. The first, Agents Aren't People: Why AI Security Requires Workload Identity, covers the broader problem space and why workload identity is the right foundation for agentic systems.

As described in the first post, modern AI systems don't run in isolation. A user opens a web app, the web app invokes an AI agent, the agent calls an MCP server, and the MCP server hits an external API. Four hops, three trust boundaries, and a question that the OAuth 2.0 spec was never designed to answer: who is acting, and on whose behalf?

This post covers the first part of that problem: how workloads and agents authenticate to OAuth Authorization Servers without shared secrets, and how they obtain client identities in environments where new services spin up continuously.

The naive approaches and why they fail

Consider a typical agentic scenario: User → Web App → AI Agent → MCP Server → External API.

The simplest implementations collapse this into one of two patterns:

Pattern A: the agent acts as itself.
The user authenticates to the web app, but from that point on, the agent uses its own identity for every downstream call. The human identity is lost after the first hop. The external API sees a request from "the AI agent", with no record of who asked or why. Audit trails are meaningless, and the agent must hold a broad, powerful token capable of reaching every downstream resource.

Pattern B: the user's token is passed through.
The user authenticates to the web app, which passes the token to the agent, which hands it to the MCP server, which uses it to call the external API. The agent's identity never appears. The aud claim exists precisely to prevent this, but resource servers frequently skip validation. A compromised agent can make arbitrary API calls as the user. Scope reduction is difficult because the token was never scoped to specific delegated tasks.

Neither approach is acceptable. We need a third pattern: one where every token in the chain carries both who authorized the action (the user) and who is performing it (the agent or service). The OAuth ecosystem has the building blocks for this, they just need to be assembled correctly.

OAuth 2.0 Foundations

The OAuth 2.0 Authorization Framework RFC 6749 is the base OAuth 2.0 RFC. The key roles are:

  • Resource Owner: the entity that can grant access (typically a user)
  • Client: the application requesting access
  • Authorization Server (AS): issues access tokens after authenticating the client and verifying authorization
  • Resource Server: the API being protected, which accepts access tokens

There are various OAuth flows, but the pattern is the same: the client presents an authorization grant to the AS's token endpoint in exchange for an access token. The grant type determines what form that credential takes.

For machine-to-machine (M2M) flows, which are the dominant pattern in agentic systems, the relevant grant is the client credentials grant. A client authenticates directly to the AS with a client_id and client_secret and receives an access token. There is no user redirect, no browser, no authorization code.

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=my-agent
&client_secret=sUp3rS3cr3t
&scope=read:data

The AS validates the credentials and returns an access token scoped to the requested resource. A client must authenticate itself to the AS as part of every token request. Here, the client_id and client_secret together constitute that client authentication.

This is the starting point for workload-to-workload OAuth. The problem is clear: client_secret is a long-lived shared secret. It must be provisioned, rotated, stored securely, and never leaked. For a single service, this is manageable. Across a fleet of agents and MCP servers, it becomes a serious operational and security liability. A leaked secret grants full access with no way to distinguish the legitimate client from an attacker.

A Better Way: Client Assertions

The Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants (RFC 7521) defines an abstract framework for replacing shared secrets with assertions, which are cryptographically verifiable tokens that prove client identity without transmitting a secret. Building on this, the JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants (RFC 7523) provides a concrete implementation using JWTs.

Instead of a client_secret, the client presents a signed JWT to the AS as a client_assertion. The JWT may be signed by the client or an issuer trusted by the AS. The AS validates the signature against the client or issuer's public keys.

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion=<signed-JWT>
&scope=read:data

The assertion JWT contains claims such as iss (the client ID or issuer), aud (the AS token endpoint), sub (the subject), and exp (expiry). The AS trusts the assertion if and only if the token's signature and claims are valid.

{
  "iss": "my-agent",
  "sub": "my-agent",
  "aud": "https://as.example.com/token",
  "jti": "a-unique-identifier",
  "iat": 1712534400,
  "exp": 1712534460
}

This eliminates the shared secret entirely - the private key never leaves the client / trusted issuer. Compromise of the AS does not expose the key, and short assertion lifetimes or the optional JTI claim  can mitigate replay attacks.

This is a great improvement. However, we now either have to manage, secure, and register the client's key material, or authenticate the client to a trusted issuer. It's turtles all the way down. For a fixed set of services this is workable, but in a dynamic environment where new agent instances spin up continuously, per-client key registration becomes an operational bottleneck.

Workload Identity Meets OAuth: SPIFFE Client Authentication

SPIFFE (Secure Production Identity Framework For Everyone) solves the key management problem at the source. Rather than requiring each workload to manage its own key pair and register the public key with each AS, SPIFFE provides a means for workloads to obtain short-lived cryptographic identities, based on attestation.

Each workload receives an SVID (SPIFFE Verifiable Identity Document): either an X.509 certificate or a JWT, both bound to a SPIFFE ID of the form spiffe://trust-domain/workload-identifier. These SVIDs are rotated automatically. No secrets need provisioning. The SPIFFE trust bundle (the set of root certificates for a trust domain) is published at a well-known endpoint.

The draft-ietf-oauth-spiffe-client-auth specification bridges SPIFFE and OAuth. It is a profile of RFC 7521/7523 that makes SPIFFE SVIDs first-class OAuth client credentials.

There are two paths:

JWT-SVID path (based on RFC 7523): The workload presents its JWT-SVID as the client_assertion, using the assertion type urn:ietf:params:oauth:client-assertion-type:jwt-spiffe.
The AS validates it against the SPIFFE trust bundle rather than a per-client registered key.

X.509-SVID path (based on OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens RFC 8705): The workload presents its X.509-SVID during the TLS handshake.
The AS validates the certificate chain against the SPIFFE trust bundle.

The critical difference from plain RFC 7523 is the trust model. Instead of registering each client's public key individually, the AS is configured with the SPIFFE trust bundle endpoint for the relevant trust domain. Any workload with a valid SVID from that trust domain can authenticate. Adding a new agent requires no AS configuration change, the agent just needs a SPIFFE identity.

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-spiffe
&client_assertion=<JWT-SVID>
&scope=read:data

SPIFFE client authentication is in active standardization. Cofide Credex supports JWT-SVID client authentication. Keycloak added preview support in September 2025. Curity has documented support for JWT-SVID client authentication. The spec is still a draft, but implementations are emerging.

This is a foundational building block for reimagining identity with open standards: SPIFFE provides the cryptographic workload identity, and draft-ietf-oauth-spiffe-client-auth provides a bridge into the OAuth ecosystem.

Client Registration

The previous sections cover how a client authenticates to the AS once it has a client_id. But how does it obtain one? In a developer portal world, a human registers an application and receives a client_id out-of-band. For agentic scenarios where clients may be short-lived, numerous, and connecting to servers they have never encountered, something more dynamic and automated is required.

OAuth defines three approaches, all present in the current MCP authorization specification.

Pre-registration

The simplest case is an existing relationship. The client is registered with the AS by the operator out-of-band, the client's ID is stored in its configuration, and presented directly at the token endpoint. Nothing is automated. For controlled deployments with a fixed set of clients and servers, that is fine. For an MCP client used by a short-lived agent, it is not.

Dynamic Client Registration (DCR)

The OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591) lets clients register themselves programmatically. The client POSTs its metadata to the AS's registration endpoint:

POST /register
Content-Type: application/json

{
  "client_name": "my-mcp-client",
  "redirect_uris": ["https://mcp.example.com/callback"],
  "grant_types": ["authorization_code"],
  "token_endpoint_auth_method": "private_key_jwt",
  "jwks_uri": "https://mcp.example.com/oauth/jwks.json"
}

The AS responds with a client_id. The bootstrapping problem remains: if the registration endpoint is open, anyone can register a client, including an attacker. If it is protected, the client needs credentials it does not yet have. The MCP specification includes dynamic client registration for backwards compatibility with earlier versions of its auth spec, but treats it as a fallback rather than the preferred path.

Client ID Metadata Documents

The preferred mechanism in the MCP specification is the emerging OAuth Client ID Metadata Document draft. Rather than registering in advance, the client uses an HTTPS URL as its client_id. When the AS encounters a URL-shaped client_id, it fetches the document at that URL to obtain the client's metadata.

{
  "client_id": "https://mcp.example.com/oauth/client-metadata.json",
  "client_name": "Example MCP Client",
  "redirect_uris": ["https://mcp.example.com/callback"],
  "grant_types": ["authorization_code"],
  "token_endpoint_auth_method": "private_key_jwt",
  "jwks_uri": "https://mcp.example.com/oauth/jwks.json"
}

No prior coordination with any specific AS is required. The client's metadata is self-describing, and any AS that supports the draft can work with any compliant client without advance setup. This directly addresses the MCP scenario where clients and servers meet for the first time.

The trade-off is that the AS now fetches arbitrary URLs supplied by unknown clients, which creates a server-side request forgery (SSRF) exposure. Implementations should apply standard mitigations: restrict fetches to HTTPS, block private address ranges, and apply request timeouts.

The priority order in practice

The MCP specification defines the order in which a client should try these mechanisms when encountering a new AS:

  1. Use pre-registered credentials, if available for this server
  2. Use Client ID Metadata Documents, if the AS advertises client_id_metadata_document_supported
  3. Fall back to Dynamic Client Registration, if the AS provides a registration_endpoint
  4. Prompt the user to enter credentials manually

In practice, many MCP client-server encounters involve no prior relationship, which makes Client ID Metadata Documents the primary path.

Registration with SPIFFE client authentication

SPIFFE client authentication opens up some options for simplifying client registration.

The simplest is to skip per-client registration entirely - any workload with a valid SVID from a trusted domain can authenticate. The SPIFFE ID from the SVID's sub claim serves as the client identifier. This works well for simple cases, but lacks per-client configuration that may be required in some cases.

Where specific configuration is required, the client can supply a URL as its client_id pointing to a Client ID Metadata Document. The SPIFFE ID provides the authentication credential while the metadata document supplies the registration details, and neither requires any per-client coordination with the AS in advance.

DCR or pre-registration remain valid options where tighter control over which clients can obtain tokens is required.

Summary

The open standards covered in this post replace the shared-secret model with a layered approach to secretless OAuth client authentication and automated registration:

  • JWT client assertions eliminate the long-lived client_secret.
  • SPIFFE client authentication eliminates per-client key registration and bridges modern workload identity systems with the OAuth 2 framework.
  • Dynamic Client Registration and Client ID Metadata Documents solve the bootstrapping problem and enable automated registration of new clients with an OAuth Authorisation Server.

Having addressed the problems around scalable and dynamic OAuth client management, the next post in this series will tackle a new problem that now presents itself: how do we provide workloads with OAuth tokens that carry delegation semantics such as who is acting, and on whose behalf all the way down the chain?

References

Layer Standard Status
Workload identity SPIFFE / SPIRE Production-ready
Client registration RFC 7591 & draft-ietf-oauth-client-id-metadata-document Production-ready
Client assertions (JWT) RFC 7521 & RFC 7523 Production-ready
mTLS client auth RFC 8705 Production-ready
OAuth client auth with SPIFFE draft-ietf-oauth-spiffe-client-auth Preview (Cofide Credex, Keycloak, Curity)

Ready to connect?

Our team is ready to walk you through how Cofide can help you to securely connect workloads with confidence.
Speak to the team