{"record":{"id":"79783cf147397156","repo":"semaphoreui/semaphore","slug":"oidc-sign-in-failed-invalid-state-try-signing-in","errorCode":null,"errorMessage":"OIDC sign-in failed: invalid state. Try signing in again.","messagePattern":"OIDC sign-in failed: invalid state\\. Try signing in again\\.","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"api/login.go","lineNumber":864,"sourceCode":"\tpid := mux.Vars(r)[\"provider\"]\n\toauthState, err := r.Cookie(\"oauthstate\")\n\n\t// Errors are shown as plain text at the current URL instead of a silent\n\t// redirect to the login page, so the user can see what went wrong.\n\t// Details stay in server logs.\n\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\thttp.Error(w, \"OIDC sign-in failed: state cookie is missing. Try signing in again.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\ts := r.FormValue(\"state\")\n\tb, err := base64.URLEncoding.DecodeString(s)\n\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\thttp.Error(w, \"OIDC sign-in failed: invalid state. Try signing in again.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tvar stateData oAuthState\n\terr = json.Unmarshal(b, &stateData)\n\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\thttp.Error(w, \"OIDC sign-in failed: invalid state. Try signing in again.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif stateData.Csrf != oauthState.Value {\n\t\thttp.Error(w, \"OIDC sign-in failed: state mismatch. Try signing in again.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tctx := context.Background()","sourceCodeStart":846,"sourceCodeEnd":882,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/api/login.go#L846-L882","documentation":"In oidcRedirect, the 'state' query parameter from the IdP callback is base64-URL-decoded before JSON-unmarshaling into oAuthState. If base64.URLEncoding.DecodeString fails (the state is not valid base64url), the handler logs the error and returns HTTP 400 'OIDC sign-in failed: invalid state. Try signing in again.' The state is treated as corrupt and the flow is aborted.","triggerScenarios":"IdP redirects back with a state query parameter that is not valid base64url (truncated, URL-encoded/modified, or fabricated); middleware or proxy rewriting the query string; manually constructed callback URL.","commonSituations":"State parameter mangled by double URL-encoding in a proxy; user editing/bookmarking the callback URL and cutting off characters; non-compliant IdP altering the state; copying the URL through a chat that wrapped/broke it.","solutions":["Restart the OIDC sign-in flow to get a fresh, intact state","Check for proxies/gateways that rewrite query strings and fix their encoding handling","Ensure the IdP returns the state parameter unmodified (compare with the issued authorize URL)","Have users open the callback link directly rather than copy/pasting through tools that alter it"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// client-side: if the IdP lands on the callback with a mangled state, restart the flow\nif (new URLSearchParams(location.search).get('state') === null || /[^A-Za-z0-9_-]/.test(state)) {\n    window.location.href = '/api/auth/oidc/' + pid\n}","preventionTips":["Never modify or truncate the callback URL","Configure proxies/gateways to pass query strings without re-encoding","Verify the IdP echoes the state parameter verbatim","Avoid URL-shorteners/redirect chains between the IdP and the callback"],"tags":["http","oidc","state","base64"],"backgroundTag":"invalid-argument-format","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}