-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Goal
Support MCP authentication via OAuth.
Progress
- Server side - see Add possibility to access request values via
#[Value]#9, Add OAuth2 gateway to implement authorization for MCP #12 and Pass scopes requested during /authorize #14 - Client side - see Return an Authorization instance from initialize() #3, but still missing a higher-level abstraction
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.