{"record":{"id":"df34ceb2033277d2","repo":"microsoft/semantic-kernel","slug":"missing-state-parameter-df34ce","errorCode":null,"errorMessage":"Missing state parameter","messagePattern":"Missing state parameter","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"python/samples/demos/mcp_with_oauth/server/mcp_simple_auth/legacy_as_server.py","lineNumber":81,"sourceCode":"        resource_server_url=None,\n    )\n\n    app = FastMCP(\n        name=\"Simple Auth MCP Server\",\n        instructions=\"A simple MCP server with simple credential authentication\",\n        auth_server_provider=oauth_provider,\n        host=server_settings.host,\n        port=server_settings.port,\n        debug=True,\n        auth=mcp_auth_settings,\n    )\n\n    @app.custom_route(\"/login\", methods=[\"GET\"])\n    async def login_page_handler(request: Request) -> Response:\n        \"\"\"Show login form.\"\"\"\n        state = request.query_params.get(\"state\")\n        if not state:\n            raise HTTPException(400, \"Missing state parameter\")\n        return await oauth_provider.get_login_page(state)\n\n    @app.custom_route(\"/login/callback\", methods=[\"POST\"])\n    async def login_callback_handler(request: Request) -> Response:\n        \"\"\"Handle simple authentication callback.\"\"\"\n        return await oauth_provider.handle_login_callback(request)\n\n    @app.tool()\n    async def get_time() -> dict[str, Any]:\n        \"\"\"\n        Get the current server time.\n\n        This tool demonstrates that system information can be protected\n        by OAuth authentication. User must be authenticated to access it.\n        \"\"\"\n\n        now = datetime.datetime.now()\n","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/microsoft/semantic-kernel/blob/c028a0c7dc4f0814cdcbaba9d998f187a41197bf/python/samples/demos/mcp_with_oauth/server/mcp_simple_auth/legacy_as_server.py#L63-L99","documentation":"Identical guard to the auth_server variant, raised as HTTPException(400) by the legacy_as_server login_page_handler (/login GET) when no 'state' query parameter is present. This is the legacy Authorization Server variant of the same flow.","triggerScenarios":"A GET /login on the legacy AS server without ?state=...; the legacy client built the login URL without state; direct navigation to /login.","commonSituations":"Migrating from the legacy AS and reusing stale bookmarks/URLs; a legacy client not emitting state; proxy stripping the query string.","solutions":["Route clients through the legacy AS authorize endpoint so state is generated and attached to /login.","Update legacy clients to include the state parameter when calling /login.","Avoid direct deep-links to /login.","Verify proxies preserve the state query parameter."],"exampleFix":"// before\n# legacy client opens /login with no query string\n\n// after\n# legacy client starts at /authorize; server redirects with state attached","handlingStrategy":"validation","validationCode":"state = request.query_params.get('state')\nif not state:\n    return PlainTextResponse('Missing state. Start the flow at /authorize.', status_code=400)\nawait oauth_provider.get_login_page(state)","typeGuard":"def has_state_param(request) -> bool:\n    return bool(request.query_params.get('state'))","tryCatchPattern":null,"preventionTips":["Update legacy clients to emit state via the authorize flow.","Remove stale bookmarks to /login.","Verify proxies preserve query parameters."],"tags":["oauth","authentication","mcp","http","input-validation","legacy"],"backgroundTag":null,"analyzedSha":"c028a0c7dc4f0814cdcbaba9d998f187a41197bf","analyzedAt":"2026-08-13T13:48:05.040Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}