Skip to content

URL returned in WWW-Authenticate when initiating OAuth flow is not always valid #1804

@nonatomiclabs

Description

@nonatomiclabs

Describe the bug

When trying to initiate the OAuth flow by making a request to https://api.githubcopilot.com/mcp/.well-known/oauth-authorization-server, the URL pointed to by the resource_metadata of the WWW-Authenticate header does not always allow to retrieve the metadata.

Affected version

We started observing this behavior since yesterday on the remote Github MCP server. I couldn't reproduce the issue locally, as there is no HTTP endpoint to test it (AFAICT).

Steps to reproduce the behavior

I'm trying to reproduce the first steps of the OAuth flow:
Image

To do so:

  • happy path:
    • I query https://api.githubcopilot.com/mcp:
      $ curl -i https://api.githubcopilot.com/mcp
      HTTP/2 401
      content-security-policy: default-src 'none'; sandbox
      content-type: text/plain; charset=utf-8
      strict-transport-security: max-age=31536000
      www-authenticate: Bearer error="invalid_request", error_description="No access token was provided in this request", resource_metadata="https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp"
      x-content-type-options: nosniff
      date: Tue, 13 Jan 2026 13:40:07 GMT
      content-length: 51
      x-github-backend: Kubernetes
      x-github-request-id: 53D5:21DA11:8DFB8C:9EC629:69664B37
      
      bad request: missing required Authorization header
    • I query the URL given by the resource_metadata field of the www-authenticate header and get the metadata 🎉:
      $ curl -i https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp
      HTTP/2 200
      access-control-allow-headers: Content-Type, Authorization, X-MCP-Readonly, X-MCP-Toolsets, Mcp-Session-Id
      access-control-allow-methods: GET, POST, OPTIONS
      access-control-allow-origin: *
      access-control-expose-headers: Mcp-Session-Id
      access-control-max-age: 86400
      content-length: 433
      content-security-policy: default-src 'none'; sandbox
      content-type: application/json
      content-type: application/json
      date: Tue, 13 Jan 2026 13:40:59 GMT
      server: github-mcp-server-remote
      strict-transport-security: max-age=31536000
      x-github-backend: Kubernetes
      x-github-request-id: 53D3:380315:46B6B0:4FAE98:69664B6B
      
      {
        "resource_name": "GitHub MCP Server",
        "resource": "https://api.githubcopilot.com/mcp",
        "authorization_servers": ["https://github.com/login/oauth"],
        "bearer_methods_supported": ["header"],
        "scopes_supported": [
          "gist",
          "notifications",
          "public_repo",
          "repo",
          "repo:status",
          "repo_deployment",
          "user",
          "user:email",
          "user:follow",
          "read:gpg_key",
          "read:org",
          "project"
        ]
      }
  • broken path 😢
    • I query another URL than the canonical one:
      $ curl -i https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp/nonsense
      HTTP/2 401
      content-length: 43
      content-security-policy: default-src 'none'; sandbox
      content-type: application/json
      content-type: text/plain; charset=utf-8
      date: Tue, 13 Jan 2026 13:42:52 GMT
      server: github-mcp-server-remote
      strict-transport-security: max-age=31536000
      www-authenticate: Bearer resource_metadata="https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp/.well-known/oauth-protected-resource/mcp/nonsense"
      x-content-type-options: nosniff
      x-github-backend: Kubernetes
      x-github-request-id: 53F6:129CBC:926C5A:A380A1:69664BDC
      
      Unauthorized: missing Authorization header
    • the custom endpoint is appended to the URL from the resource_metadata and this URL doesn't give me the metadata anymore:
      $ curl -i https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp/.well-known/oauth-protected-resource/mcp/nonsense
      HTTP/2 401
      content-length: 43
      content-security-policy: default-src 'none'; sandbox
      content-type: application/json
      content-type: text/plain; charset=utf-8
      date: Tue, 13 Jan 2026 13:44:37 GMT
      server: github-mcp-server-remote
      strict-transport-security: max-age=31536000
      www-authenticate: Bearer resource_metadata="https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp/.well-known/oauth-protected-resource/mcp/.well-known/oauth-protected-resource/mcp/nonsense"
      x-content-type-options: nosniff
      x-github-backend: Kubernetes
      x-github-request-id: 53C2:85858:330B97:392F02:69664C45
      
      Unauthorized: missing Authorization header
      (As a funny side effect, we can now notice that the resource_metadata gets longer and longer every time we make the call)

Expected vs actual behavior

To me, this looks like this breaks the expectations from RFC 9728 (emphasis mine):

  1. The client makes a request to a protected resource without presenting an access token.
  2. The resource server responds with a WWW-Authenticate header including the URL of the protected resource metadata.
  3. The client fetches the protected resource metadata from this URL.
  4. The resource server responds with the protected resource metadata according to Section 3.2.

This looks to me like trying to access anything under /mcp should return https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp.

For context, this broke code where we try to initiate the flow by querying https://api.githubcopilot.com/mcp/.well-known/oauth-authorization-server (and not https://api.githubcopilot.com/mcp).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions