semaphoreui/semaphore · error
OIDC sign-in failed: state mismatch. Try signing in again.
Error message
OIDC sign-in failed: state mismatch. Try signing in again.
What it means
The final CSRF check of the OIDC flow: the decoded state's Csrf field is compared to the value stored in the state cookie (oauthState.Value). If they differ, the handler returns HTTP 400 'OIDC sign-in failed: state mismatch. Try signing in again.' This is the standard OAuth2 state-mismatch protection against CSRF/login-replay attacks and aborts the flow whenever the returned state was not the one issued to this browser.
Solutions
- Close other login tabs and restart the sign-in flow so only one state cookie/flow exists
- Don't use the back button or bookmarked callback URLs — start a fresh login
- Ensure all Semaphore replicas share the same cookie secret/config so state validation is consistent
- If it recurs legitimately, check for proxies sharing/mixing cookies across users
Defensive patterns
Strategy: retry
Validate before calling
// before opening a second login, note that concurrent logins invalidate each other's state cookie: // serialize logins: only one tab may run /api/auth/oidc/<pid> at a time
Try / catch
// on 'state mismatch' at the callback, restart sign-in from the beginning: window.location.href = '/api/auth/oidc/' + pid // fresh state + cookie pair
Prevention
- Avoid parallel login tabs in the same browser (last cookie wins)
- Never reuse bookmarked/back-button callback URLs
- Keep all replicas on the same cookie-signing configuration
- Complete login promptly and in a single flow to keep state and cookie in sync
When it happens
Trigger: Callback whose state payload is valid but does not equal the value in the browser's state cookie — user has multiple concurrent login tabs overwriting the state cookie; cookie for a different/older flow; replayed callback URL from another session; cross-site callback forgery attempt.
Common situations: User opening the login page in two tabs (each starts a flow, last cookie wins, first IdP return mismatches); load-balanced instances with different cookie signing; user using back button to redo an old callback; a deliberate CSRF probe (expected rejection).
Related errors
- Account linking must be initiated with a POST request.
- You must be signed in to link an external account.
- OIDC sign-in failed: state cookie is missing. Try signing…
- OIDC sign-in failed: invalid state. Try signing in again.
- Unauthorized
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/c9ffdd1c96e01e38.
Report an issue: GitHub.
Appendix: source
Thrown at api/login.go:878
b, err := base64.URLEncoding.DecodeString(s)
if err != nil {
log.Error(err.Error())
http.Error(w, "OIDC sign-in failed: invalid state. Try signing in again.", http.StatusBadRequest)
return
}
var stateData oAuthState
err = json.Unmarshal(b, &stateData)
if err != nil {
log.Error(err.Error())
http.Error(w, "OIDC sign-in failed: invalid state. Try signing in again.", http.StatusBadRequest)
return
}
if stateData.Csrf != oauthState.Value {
http.Error(w, "OIDC sign-in failed: state mismatch. Try signing in again.", http.StatusBadRequest)
return
}
ctx := context.Background()
_oidc, oauth, err := getOidcProvider(pid, ctx, r.URL.Path)
if err != nil {
log.Error(err.Error())
http.Error(w, "Failed to initialize OIDC provider. Contact your administrator.", http.StatusInternalServerError)
return
}
provider, ok := util.Config.OidcProviders[pid]
if !ok {
log.Error(fmt.Errorf("no such provider: %s", pid))
http.Error(w, "Unknown OIDC provider.", http.StatusNotFound)
return
}View on GitHub (pinned to 1774ccb71a)