Add Clerk OAuth provider#3677
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Test Failure Analysis
Summary: The Root Cause: In introspect_auth = None # line 144 — type: None
if self._client_id and self._client_secret:
introspect_auth = (self._client_id, self._client_secret) # type: tuple[str, str]
elif self._client_id:
introspect_data_payload["client_id"] = self._client_id
introspect_response = await client.post(
self._introspection_url,
data=introspect_data_payload,
headers={"User-Agent": "FastMCP-Clerk-OAuth"},
auth=introspect_auth, # line 154 — ty error: None is not a valid AuthType
)
Suggested Solution: In # Replace lines 150-155 with:
post_kwargs: dict = {
"data": introspect_data_payload,
"headers": {"User-Agent": "FastMCP-Clerk-OAuth"},
}
if introspect_auth is not None:
post_kwargs["auth"] = introspect_auth
introspect_response = await client.post(
self._introspection_url,
**post_kwargs,
)Alternatively, annotate if introspect_auth is not None:
introspect_response = await client.post(
self._introspection_url,
data=introspect_data_payload,
headers={"User-Agent": "FastMCP-Clerk-OAuth"},
auth=introspect_auth,
)
else:
introspect_response = await client.post(
self._introspection_url,
data=introspect_data_payload,
headers={"User-Agent": "FastMCP-Clerk-OAuth"},
)Detailed AnalysisFailing check: Error message from CI: Why: File: Related Files
|
|
Hey @jlowin — could you take a look at this? The failing test appears to be a pre-existing issue on main, unrelated to the Clerk OAuth changes in this PR. The marvin-context-protocol bot analysis highlights that get_http_request() does not restore headers when called directly inside a background task (without using CurrentRequest() / CurrentHeaders()), which causes the failure. |
jlowin
left a comment
There was a problem hiding this comment.
Thanks for putting this together!
A few things to address before this can ship:
Should likely use OIDCProxy instead of OAuthProxy. Clerk is an OIDC provider, and assuming it publishes a .well-known/openid-configuration endpoint, this should extend OIDCProxy rather than OAuthProxy. OIDCProxy handles JWKS discovery, JWT verification, and endpoint configuration automatically — no custom ClerkTokenVerifier needed. Take a look at Auth0Provider as a template; it's about 130 lines with no custom token verifier. The current approach makes two HTTP calls per token verification where local JWT verification would be zero. (Worth confirming that Clerk's OIDC discovery works as expected here — if it does, this simplifies things a lot.)
Audience verification is required. The current ClerkTokenVerifier reads the audience from introspection but never validates it against the configured client_id. We need to verify that tokens were actually issued for the correct application. OIDCProxy handles this naturally — audience is a first-class parameter (see how Auth0Provider uses it), and the underlying JWTVerifier checks it during JWT validation.
Scope fallback is too permissive. When introspection fails, the verifier assumes the token has all required scopes (lines 308-309). That means scope checking only works when introspection is healthy — if it's down, every token passes. This would likely also be resolved by the OIDCProxy switch, since scopes would come from the JWT claims directly.
If OIDCProxy works with Clerk's discovery endpoint, the switch should simplify this significantly and address all three concerns at once.
|
@jlowin Thanks for the thoughtful review — really appreciate the detailed feedback. On the OIDCProxy vs OAuthProxy point: That said, our current approach is With
We chose the latter to keep the auth pipeline simpler for downstream consumers and consistent with existing providers. On audience validation: On scope validation fallback:
Summary
|
|
@jlowin please take a look — I’ve fixed the oauth/token_info issue using Basic Auth. |
jlowin
left a comment
There was a problem hiding this comment.
Thanks, I understand the reasoning better now — the Google provider is a closer analogue than Auth0 for what Clerk needs, and this follows that pattern. Withdrawing the OIDCProxy ask.
Two things to address before this can merge:
Flip the verification order. The Google provider calls tokeninfo first (auth metadata: aud, sub, scopes, expiry) and userinfo second (profile enrichment, gracefully degraded). Clerk does it backwards: userinfo first, introspection second. This means if introspection reveals the token is inactive or has a wrong audience, the userinfo call was wasted. More importantly, security-critical checks should come first. Introspect first, then fetch userinfo only if the token passes, and make userinfo failure non-fatal like Google does.
Fix the active field default. introspect_data.get("active", True) treats a missing active field as active. RFC 7662 requires active in the response — if it's absent, the response is malformed and should be treated as a rejection, not a pass.
|
@jlowin Thanks — updated.
|
Description
Add a Clerk OAuth provider to the FastMCP server auth stack, mirroring the existing Google style providers.
Contribution type
Checklist
uv run prek run --all-filesand all checks pass