Skip to content

Authentication #11

@thekid

Description

@thekid

Goal

Support MCP authentication via OAuth.

Progress


Client side

With #3, we can call initialize() and check for an io.modelcontextprotocol.Authorization instance.

Step 1 - Initial request

use io\modelcontextprotocol\{McpClient, Authorization};

$client= new McpClient('http://localhost:3001');
if ($client->initialize() instanceof Authorization) {
  // Start authentication
}

Step 2 - OAuth discovery

Fetch the http://localhost:8080/.well-known/oauth-authorization-server resource
Parse the JSON metadata

Should be provide a fetch() method on the McpClient or should this be abstracted more?

3 - OAuth registration (optional)

Post the registration data
Parse and rember the client ID

{
  "client_name": "My MCP Client",
  "redirect_uris": ["http://localhost:8080/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}

4 - Actual authentication

Generate a challenge
(Implementation depends on execution context, e.g. redirect the user)

5 - Token exchange

Once the redirect URL is reached:
With the code received, open /oauth/token and fetch the access token


Server side

From the point of view of a server running at mcp.example.com, the following happens:

1 - Initial request

The client makes an unauthenticated request
We respond with a 401 error and the WWW-Authenticate: Bearer header

2 - OAuth discovery

The client requests the URI /.well-known/oauth-authorization-server
We responde with a JSON structure

{
  "issuer": "https://mcp.example.com",
  "authorization_endpoint": "https://mcp.example.com/oauth/authorize",
  "token_endpoint": "https://mcp.example.com/oauth/token",
  "registration_endpoint": "https://mcp.example.com/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}

3 - OAuth registration (optional)

The client registers itself
We store this somewhere and return the client ID

{
  "client_id": "abc123-generated-client-id",
  "client_name": "My MCP Client",
  "redirect_uris": ["http://localhost:8080/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}

Otherwise, the redirect URI would come from our storage

4 - Actual authentication

The client redirects the user to the /oauth/authorize endpoint using PKCE
We authenticate the user, calculate a one-time code and redirect them back to the requested redirect URI

5 - Token exchange

The client passes the one-time code
We respond with the access token

{
  "access_token": "ey...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "...."
}

This can be implemented with a server-side session containing the user. The code is put in there during authentication, and removed once the token exchange has finished, preventing replay attacks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions