diff --git a/asgi_app.py b/asgi_app.py index 14bce87..0e54a1c 100644 --- a/asgi_app.py +++ b/asgi_app.py @@ -139,22 +139,54 @@ async def mcp_protocol_handler(request: Request): token = auth_header.split(" ")[1] try: # Validate Clerk JWT token (required) - from clerk_backend_api import Clerk - clerk = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY")) - jwt_claims = clerk.jwt_templates.verify_token(token) - user_id = jwt_claims.get("sub") + from clerk_backend_api import Clerk, models + import jwt - if not user_id: - logger.error("JWT token validation failed - no user_id in claims") + # First, decode JWT token without verification to get session_id + try: + decoded_token = jwt.decode(token, options={"verify_signature": False}) + session_id = decoded_token.get("sid") or decoded_token.get("session_id") + except Exception as e: + logger.error(f"JWT token decoding failed: {e}") raise HTTPException( status_code=401, - detail="Invalid token - no user_id in claims" + detail="Invalid JWT token format" ) - logger.info(f"Bearer JWT token validated for user: {user_id}") - # Add user info to request state - request.state.user_id = user_id - request.state.token_scopes = jwt_claims.get("scopes", ["read", "search"]) + if not session_id: + logger.error("No session_id found in JWT token") + raise HTTPException( + status_code=401, + detail="Invalid token - no session_id in claims" + ) + + # Now verify the session with Clerk + clerk = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY")) + + try: + # Use deprecated but working sessions.verify method + session = clerk.sessions.verify(session_id=session_id, token=token) + user_id = session.user_id if session else None + + if not user_id: + logger.error("Session verification failed - no user_id") + raise HTTPException( + status_code=401, + detail="Invalid session - no user_id" + ) + + logger.info(f"Bearer JWT token validated for user: {user_id}") + # Add user info to request state + request.state.user_id = user_id + request.state.session_id = session_id + request.state.token_scopes = ["read", "search"] # Default scopes + + except models.ClerkErrors as e: + logger.error(f"Clerk session verification failed: {e}") + raise HTTPException( + status_code=401, + detail="Session verification failed" + ) except HTTPException: # Re-raise HTTPException as-is diff --git a/mcp_auth_http_simple.py b/mcp_auth_http_simple.py index 1b651ab..87b2d51 100644 --- a/mcp_auth_http_simple.py +++ b/mcp_auth_http_simple.py @@ -189,6 +189,62 @@ async def register_client(request: Request): "token_endpoint_auth_method": "none" }) +@router.post("/auth/callback") +async def oauth_callback_post(request: Request): + """OAuth callback POST endpoint for token exchange""" + + # Parse form data (standard OAuth token exchange format) + form_data = await request.form() + grant_type = form_data.get("grant_type") + code = form_data.get("code") + redirect_uri = form_data.get("redirect_uri") + client_id = form_data.get("client_id") + code_verifier = form_data.get("code_verifier") + + logger.info(f"OAuth callback POST - grant_type: {grant_type}") + logger.info(f"Code: {code[:20] if code else 'None'}...") + logger.info(f"Client ID: {client_id}") + logger.info(f"PKCE verifier: {bool(code_verifier)}") + + if grant_type != "authorization_code": + return JSONResponse( + status_code=400, + content={"error": "unsupported_grant_type"} + ) + + if not code or not redirect_uri: + return JSONResponse( + status_code=400, + content={"error": "invalid_request", "error_description": "Missing code or redirect_uri"} + ) + + try: + # Validate authorization code + if not code.startswith("clerk_auth_"): + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Invalid authorization code"} + ) + + # TODO: In production, validate code against stored session + # For now, we'll return a placeholder response + + # Generate or retrieve actual Clerk JWT token + # This should be the actual JWT token from Clerk authentication + return JSONResponse({ + "access_token": "PLACEHOLDER_CLERK_JWT_TOKEN", + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read search" + }) + + except Exception as e: + logger.exception(f"OAuth callback POST failed: {e}") + return JSONResponse( + status_code=500, + content={"error": "server_error", "error_description": str(e)} + ) + @router.post("/token") async def token_endpoint(request: Request): """OAuth 2.1 Token Endpoint - exchanges code for Clerk JWT"""