microsoft/semantic-kernel · warning · HTTPException
Missing state parameter
Error message
Missing state parameter
What it means
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.
Source
Thrown at python/samples/demos/mcp_with_oauth/server/mcp_simple_auth/legacy_as_server.py:81
resource_server_url=None,
)
app = FastMCP(
name="Simple Auth MCP Server",
instructions="A simple MCP server with simple credential authentication",
auth_server_provider=oauth_provider,
host=server_settings.host,
port=server_settings.port,
debug=True,
auth=mcp_auth_settings,
)
@app.custom_route("/login", methods=["GET"])
async def login_page_handler(request: Request) -> Response:
"""Show login form."""
state = request.query_params.get("state")
if not state:
raise HTTPException(400, "Missing state parameter")
return await oauth_provider.get_login_page(state)
@app.custom_route("/login/callback", methods=["POST"])
async def login_callback_handler(request: Request) -> Response:
"""Handle simple authentication callback."""
return await oauth_provider.handle_login_callback(request)
@app.tool()
async def get_time() -> dict[str, Any]:
"""
Get the current server time.
This tool demonstrates that system information can be protected
by OAuth authentication. User must be authenticated to access it.
"""
now = datetime.datetime.now()
View on GitHub (pinned to c028a0c7dc)
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.
Example fix
// before # legacy client opens /login with no query string // after # legacy client starts at /authorize; server redirects with state attached
Defensive patterns
Strategy: validation
Validate before calling
state = request.query_params.get('state')
if not state:
return PlainTextResponse('Missing state. Start the flow at /authorize.', status_code=400)
await oauth_provider.get_login_page(state) Type guard
def has_state_param(request) -> bool:
return bool(request.query_params.get('state')) Prevention
- Update legacy clients to emit state via the authorize flow.
- Remove stale bookmarks to /login.
- Verify proxies preserve query parameters.
When it happens
Trigger: A GET /login on the legacy AS server without ?state=...; the legacy client built the login URL without state; direct navigation to /login.
Common situations: Migrating from the legacy AS and reusing stale bookmarks/URLs; a legacy client not emitting state; proxy stripping the query string.
Related errors
- Missing state parameter
- Missing state parameter
- Missing username, password, or state parameter
- Invalid parameter types
- OAuth error: {self.callback_data['error']}
AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13).
Data as JSON: /api/errors/df34ceb2033277d2.
Report an issue: GitHub.