Skip to content

Commit 8820bab

Browse files
praboud-antdsp-ant
authored andcommitted
Clean up provider interface
1 parent 7e70971 commit 8820bab

File tree

4 files changed

+30
-27
lines changed

4 files changed

+30
-27
lines changed

src/mcp/server/auth/handlers/authorize.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ class AuthorizationRequest(BaseModel):
3636

3737
response_type: Literal["code"] = Field(..., description="Must be 'code' for authorization code flow")
3838
code_challenge: str = Field(..., description="PKCE code challenge")
39-
code_challenge_method: Literal["S256"] = Field("S256", description="PKCE code challenge method")
39+
code_challenge_method: Literal["S256"] = Field("S256", description="PKCE code challenge method, must be S256")
4040
state: Optional[str] = Field(None, description="Optional state parameter")
41-
scope: Optional[str] = Field(None, description="Optional scope parameter")
41+
scope: Optional[str] = Field(None, description="Optional scope; if specified, should be a space-separated list of scope strings")
4242

4343
class Config:
4444
extra = "ignore"
@@ -113,12 +113,21 @@ async def authorization_handler(request: Request) -> Response:
113113
code_challenge=auth_request.code_challenge,
114114
redirect_uri=redirect_uri,
115115
)
116-
117-
response = RedirectResponse(url="", status_code=302, headers={"Cache-Control": "no-store"})
118116

119117
try:
120118
# Let the provider handle the authorization flow
121-
await provider.authorize(client, auth_params, response)
119+
authorization_code = await provider.create_authorization_code(client, auth_params)
120+
response = RedirectResponse(url="", status_code=302, headers={"Cache-Control": "no-store"})
121+
122+
# Redirect with code
123+
parsed_uri = urlparse(str(auth_params.redirect_uri))
124+
query_params = [(k, v) for k, vs in parse_qs(parsed_uri.query) for v in vs]
125+
query_params.append(("code", authorization_code))
126+
if auth_params.state:
127+
query_params.append(("state", auth_params.state))
128+
129+
redirect_url = urlunparse(parsed_uri._replace(query=urlencode(query_params)))
130+
response.headers["location"] = redirect_url
122131

123132
return response
124133
except Exception as e:

src/mcp/server/auth/handlers/token.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ async def token_handler(request: Request):
8888

8989
match token_request:
9090
case AuthorizationCodeRequest():
91+
# TODO: verify that the redirect URIs match; does the client actually provide this?
92+
# see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
93+
# TODO: enforce TTL on the authorization code
94+
9195
# Verify PKCE code verifier
9296
expected_challenge = await provider.challenge_for_authorization_code(
9397
client_info, token_request.code

src/mcp/server/auth/provider.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,15 @@ def clients_store(self) -> OAuthRegisteredClientsStore:
7272
"""
7373
...
7474

75-
# TODO: do we really want to be putting the response in this method?
76-
async def authorize(self,
75+
async def create_authorization_code(self,
7776
client: OAuthClientInformationFull,
78-
params: AuthorizationParams,
79-
response: Response) -> None:
77+
params: AuthorizationParams) -> str:
8078
"""
81-
Begins the authorization flow, which can be implemented by this server or via redirection.
82-
Must eventually issue a redirect with authorization response or error to the given redirect URI.
83-
84-
Args:
85-
client: The client requesting authorization.
86-
params: Parameters for the authorization request.
87-
response: The response object to write to.
79+
Generates and stores an authorization code as part of completing the /authorize OAuth step.
80+
81+
Implementations SHOULD generate an authorization code with at least 160 bits of entropy,
82+
and MUST generate an authorization code with at least 128 bits of entropy.
83+
See https://datatracker.ietf.org/doc/html/rfc6749#section-10.10.
8884
"""
8985
...
9086

tests/server/fastmcp/auth/test_auth_integration.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,9 @@ def __init__(self):
6464
def clients_store(self) -> OAuthRegisteredClientsStore:
6565
return self.client_store
6666

67-
async def authorize(self,
67+
async def create_authorization_code(self,
6868
client: OAuthClientInformationFull,
69-
params: AuthorizationParams,
70-
response: Response):
69+
params: AuthorizationParams) -> str:
7170
# Generate an authorization code
7271
code = f"code_{int(time.time())}"
7372

@@ -78,14 +77,9 @@ async def authorize(self,
7877
"redirect_uri": params.redirect_uri,
7978
"expires_at": int(time.time()) + 600, # 10 minutes
8079
}
81-
82-
# Redirect with code
83-
query = {"code": code}
84-
if params.state:
85-
query["state"] = params.state
86-
87-
redirect_url = f"{params.redirect_uri}?" + "&".join([f"{k}={v}" for k, v in query.items()])
88-
response.headers["location"] = redirect_url
80+
81+
return code
82+
8983

9084
async def challenge_for_authorization_code(self,
9185
client: OAuthClientInformationFull,

0 commit comments

Comments
 (0)