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
- 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.
- Do not restart the OAuth server mid-flow — its state_mapping is in-memory only and does not survive a restart.
- If running behind multiple processes, use a single server instance for the sample (it is not designed for shared/clustered state).
- 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
- Never restart the in-memory OAuth sample server mid-flow.
- Complete authorize -> login -> callback -> token exchange without server restarts.
- Run only a single instance of the sample provider.
- Treat a 400 'Invalid state' as a signal to restart the flow, not retry the same state.
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
- Invalid credentials
- Invalid authorization code
- Refresh tokens not supported
- OAuth error: {self.callback_data['error']}
- Timeout waiting for OAuth callback
AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13).
Data as JSON: /api/errors/a5758fa0cc4898b8.
Report an issue: GitHub.