Skip to main content
Lesson 8 of 14 · ChatGPT Apps SDK and Plugins: MCP Servers and Interfaces

Authentication and Security

Lesson 8 of 14 Updated November 3, 2026 Developer
On this page
  1. Who needs to authenticate
  2. What the specification requires
  3. How ChatGPT registers as a client
  4. The user-facing flow
  5. Declaring which tools require an account
  6. Verifying every token
  7. Identifying ChatGPT as the client
  8. OpenAI’s security principles
  9. Testing authentication
  10. Key takeaways

Lesson rewritten on November 3, 2026 based on OpenAI’s Authentication and Security & Privacy plugin documentation. The previous version described API key authentication and an auth configuration in a non-existent manifest; ChatGPT cannot present a custom API key and instead follows the authorization flow defined in the MCP specification (OAuth 2.1). Sources: Authentication · Security & Privacy.

Who needs to authenticate

Many MCP servers can operate in read-only mode anonymously. However, any server that exposes user-specific data or performs write operations must authenticate the user. The authorization contract follows the MCP specification, which is identical in ChatGPT and Codex: an OAuth 2.1 flow involving three actors.

  • The resource server: Your MCP server, which exposes tools and verifies the access token on every request.
  • The authorization server: Your identity provider, which issues tokens and publishes its discovery metadata.
  • The client: The OpenAI host (ChatGPT or Codex), acting on behalf of the user.

OpenAI strongly recommends relying on an established identity provider rather than building authentication from scratch; their documentation specifically points to Auth0 and Stytch.

What the specification requires

Protected Resource Metadata. Your MCP server must publish a document, for example at https://your-mcp.example.com/.well-known/oauth-protected-resource, containing its canonical identifier (resource), its authorization servers (authorization_servers), and optionally the supported scopes (scopes_supported). When a request arrives without valid authentication, the server must respond with 401 Unauthorized and a header pointing to this document:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://your-mcp.example.com/.well-known/oauth-protected-resource", scope="files:read"

Authorization Server Metadata, located at /.well-known/oauth-authorization-server or /.well-known/openid-configuration, must include the issuer (issuer), authorization and token endpoints, and code_challenge_methods_supported containing S256. Without this, the MCP server is unsupported, as PKCE is mandatory.

The resource parameter end-to-end. ChatGPT adds resource=… to authorization and token requests. Your authorization server must copy this into the token (often as the aud claim), and your MCP server must reject any token not issued specifically for it.

How ChatGPT registers as a client

Three methods coexist. The preferred method is CIMD (Client ID Metadata Documents): ChatGPT uses the URL of an HTTPS-hosted metadata document as the client_id, which your authorization server reads. Enable this by publishing client_id_metadata_document_supported: true. ChatGPT supports two authentication methods at the token endpoint: none (public client protected by PKCE) and private_key_jwt (signed assertion verifiable with the public key published by ChatGPT). Dynamic Client Registration (DCR) remains possible if you publish a registration_endpoint: ChatGPT registers once per connection and reuses that client. A predefined OAuth client is the third option.

Regarding redirects, copy the exact URI displayed on the MCP server management page in ChatGPT into your authorization server’s allowlist. This URI depends on support for Issuer Identification (RFC 9207, the iss parameter returned in every authorization response).

The user-facing flow

  1. ChatGPT reads your server’s protected resource metadata.
  2. It identifies itself as an OAuth client (via CIMD, DCR, or a predefined client).
  3. On the first call to a protected tool, it initiates the authorization code + PKCE flow: the user logs in and consents to the requested scopes.
  4. ChatGPT exchanges the code for a token and attaches it to subsequent MCP requests (Authorization: Bearer …).
  5. Your server verifies the token on every request before executing the tool.

Declaring which tools require an account

ChatGPT only displays its account linking interface if your server signals that OAuth is available or required. This requires both halves: metadata and a runtime error.

First, the metadata: each tool declares its policy in _meta.securitySchemes. noauth means callable anonymously, while oauth2 requires a token with the indicated scopes. Declaring both signals that the tool can start without an account, but linking unlocks more functionality. Declare this per tool, even if the entire server follows the same policy.

server.registerTool(
  "create_doc",
  {
    title: "Create Document",
    description: "Creates a new document in the user's account.",
    inputSchema: { title: z.string().min(1) },
    _meta: { securitySchemes: [{ type: "oauth2", scopes: ["docs.write"] }] },
  },
  async ({ title }, extra) => {
    const token = await verifyToken(extra); // signature, iss, aud, exp, scopes
    if (!token) {
      return {
        isError: true,
        content: [{ type: "text", text: "Login required to create a document." }],
        _meta: {
          "mcp/www_authenticate": [
            'Bearer resource_metadata="https://your-mcp.example.com/.well-known/oauth-protected-resource", error="insufficient_scope", error_description="Link your account to continue"',
          ],
        },
      };
    }
    // … create document for token.sub
  }
);

Second, the runtime error: when the token is missing or invalid, the tool must return an error result containing _meta["mcp/www_authenticate"] with error and error_description parameters. This content is what actually triggers the login interface. verifyToken is your verification function; the Python and TypeScript MCP SDKs provide helpers for writing it.

Verifying every token

Once the flow completes, ChatGPT attaches the token as-is to every request. Treat it as untrusted and perform all standard resource server verifications yourself: signature validation using keys published by your authorization server (JWKS) and the iss claim; time validity (exp, nbf); audience (aud or resource) and required scopes; followed by your own business rules. On failure: return 401 and a WWW-Authenticate header, which restarts the OAuth flow.

Identifying ChatGPT as the client

To verify that a request genuinely comes from ChatGPT, two methods exist. ChatGPT presents an OpenAI-managed client certificate during the TLS connection (mTLS). Verify that it chains to the intermediate authority published by OpenAI, is valid for client authentication, and has the dnsName mtls.prod.connectors.openai.com. Do not pin the leaf certificate fingerprint, as OpenAI may rotate it. You can also filter based on published IP address ranges. mTLS authenticates ChatGPT; OAuth continues to authenticate the user.

ChatGPT does not support machine-to-machine permissions (client credentials, service accounts, JWT bearer assertions), custom API keys, or client-provided mTLS certificates.

OpenAI’s security principles

  • Least privilege: Request only the scopes, storage, and network access necessary.
  • Explicit consent: Users must understand when they are linking an account or granting write access; use host confirmations for destructive actions.
  • Defense in depth: Assume prompt injections and malicious inputs will reach your server. Validate all inputs server-side, even those provided by the model, and maintain audit logs.

Regarding data: include only what the request needs in structuredContent, publish a retention policy, honor deletion requests, and mask personal data in logs. Regarding the interface: the component runs in an isolated iframe under a strict Content Security Policy, with no access to window.alert, window.prompt, window.confirm, or navigator.clipboard. Regarding operations: conduct a security review before launch, alert on repeated authentication failures, and keep dependencies up to date.

Testing authentication

Start with a development tenant that issues short-lived tokens, restrict access to trusted testers before opening up, and plan for revocation, refresh, and scope changes. Treat a missing or expired token as a lack of authentication, providing a helpful error message. The Auth settings in MCP Inspector allow you to trace the OAuth flow step-by-step to see where it breaks.

Key takeaways

  • Private data or writes: OAuth 2.1 per the MCP specification, with PKCE S256
  • The server publishes protected resource metadata and responds with 401 + WWW-Authenticate
  • CIMD is the preferred registration method; DCR and predefined clients remain options
  • Linking interface: securitySchemes per tool and an error containing _meta["mcp/www_authenticate"]
  • Verify every token on every request; mTLS identifies ChatGPT, OAuth identifies the user