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

  1. Route clients through the legacy AS authorize endpoint so state is generated and attached to /login.
  2. Update legacy clients to include the state parameter when calling /login.
  3. Avoid direct deep-links to /login.
  4. 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

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


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/df34ceb2033277d2. Report an issue: GitHub.