microsoft/semantic-kernel · error · HTTPException

Invalid state parameter

Error message

Invalid state parameter

What it means

Thrown during the MCP OAuth sample's simple-login callback. The server keeps OAuth 'state' tokens in an in-memory dict (self.state_mapping); when the login form is POSTed back, the supplied 'state' value has no matching entry. This is the standard OAuth2 CSRF/state-protection check failing because the state is unknown, already consumed, or lost on a server restart.

Source

Thrown at python/samples/demos/mcp_with_oauth/server/mcp_simple_auth/simple_auth_provider.py:160

        username = form.get("username")
        password = form.get("password")
        state = form.get("state")

        if not username or not password or not state:
            raise HTTPException(400, "Missing username, password, or state parameter")

        # Ensure we have strings, not UploadFile objects
        if not isinstance(username, str) or not isinstance(password, str) or not isinstance(state, str):
            raise HTTPException(400, "Invalid parameter types")

        redirect_uri = await self.handle_simple_callback(username, password, state)
        return RedirectResponse(url=redirect_uri, status_code=302)

    async def handle_simple_callback(self, username: str, password: str, state: str) -> str:
        """Handle simple authentication callback and return redirect URI."""
        state_data = self.state_mapping.get(state)
        if not state_data:
            raise HTTPException(400, "Invalid state parameter")

        redirect_uri = state_data["redirect_uri"]
        code_challenge = state_data["code_challenge"]
        redirect_uri_provided_explicitly = state_data["redirect_uri_provided_explicitly"] == "True"
        client_id = state_data["client_id"]
        resource = state_data.get("resource")  # RFC 8707

        # These are required values from our own state mapping
        assert redirect_uri is not None
        assert code_challenge is not None
        assert client_id is not None

        # Validate demo credentials
        if username != self.settings.demo_username or password != self.settings.demo_password:
            raise HTTPException(401, "Invalid credentials")

        # Create MCP authorization code
        new_code = f"mcp_{secrets.token_hex(16)}"

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Restart the entire flow from the client: begin a new authorization request so the server generates and stores a fresh state token, then complete login in one continuous server session.
  2. Do not restart the OAuth server mid-flow — its state_mapping is in-memory only and does not survive a restart.
  3. If running behind multiple processes, use a single server instance for the sample (it is not designed for shared/clustered state).
  4. Verify the login form's hidden 'state' input is being sent unchanged and that no proxy/browser is stripping form fields.

Example fix

// Not a code bug — operational. Ensure no server restart between GET /login and POST /login/callback.
// If you need persistence, replace the in-memory dict with a shared store in the sample provider.
Defensive patterns

Strategy: validation

Validate before calling

# Before submitting the login form, ensure the state token is still valid by
# completing the flow in a single server session. Server-side, optionally check:
if state not in provider.state_mapping:
    # prompt the user to restart the authorization flow
    return RedirectResponse(url='/authorize', status_code=302)

Try / catch

try:
    redirect = await provider.handle_simple_callback(username, password, state)
except HTTPException as e:
    if e.status_code == 400 and 'state' in e.detail.lower():
        # restart the OAuth flow from the beginning
        ...
    raise

Prevention

When it happens

Trigger: POST to /login/callback with a 'state' field that is absent from self.state_mapping — e.g. the user opened the login page, the server restarted (in-memory store wiped), then submitted the form; or the state was already consumed/removed after a prior successful callback (line 198 deletes it); or a crafted/tampered state.

Common situations: Restarting the demo MCP OAuth server between starting the auth flow and submitting credentials; double-submitting the login form; running multiple server replicas with no shared state store; clock/session expiry in long-held browser tabs.

Related errors


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