{"record":{"id":"71f2be8883702139","repo":"vxcontrol/pentagi","slug":"auth-tokenexpired","errorCode":"Auth.TokenExpired","errorMessage":"state signature expired","messagePattern":"state signature expired","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"backend/pkg/server/services/auth.go","lineNumber":729,"sourceCode":"\t}\n\n\tif _, ok := stateData[\"provider\"]; !ok {\n\t\terr := fmt.Errorf(\"missing required field: provider\")\n\t\tlogger.FromContext(c).WithError(err).Errorf(\"error on validating state data\")\n\t\tresponse.Error(c, response.ErrAuthInvalidAuthorizationState, err)\n\t\treturn nil, err\n\t}\n\n\texp, err := strconv.ParseInt(expStr, 10, 64)\n\tif err != nil {\n\t\tlogger.FromContext(c).WithError(err).Errorf(\"error on parsing expiration time\")\n\t\tresponse.Error(c, response.ErrAuthInvalidAuthorizationState, err)\n\t\treturn nil, err\n\t}\n\n\tif time.Now().Unix() > exp {\n\t\tlogger.FromContext(c).Errorf(\"error on checking expiration time\")\n\t\terr := fmt.Errorf(\"state signature expired\")\n\t\tresponse.Error(c, response.ErrAuthTokenExpired, err)\n\t\treturn nil, err\n\t}\n\n\treturn stateData, nil\n}\n\nfunc (s *AuthService) setCallbackCookie(\n\tw http.ResponseWriter, r *http.Request,\n\tname, value string, maxAge int,\n\tsameSite http.SameSite,\n) {\n\t// Check both direct TLS and X-Forwarded-Proto header (for reverse proxy setups)\n\tuseTLS := r.TLS != nil || r.Header.Get(\"X-Forwarded-Proto\") == \"https\"\n\n\tc := &http.Cookie{\n\t\tName:     name,\n\t\tValue:    value,","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/services/auth.go#L711-L747","documentation":"parseState checks the 'exp' timestamp embedded in the signed OAuth state payload against the current time. If the state has aged past its expiration, the callback rejects it with Auth.TokenExpired. OAuth state is single-use and short-lived by design to prevent replay and CSRF, so expired state is a normal, expected rejection.","triggerScenarios":"User completes the OAuth consent screen after the state TTL has elapsed; user bookmarked the callback URL and reuses it later; slow redirect chain or network delays push completion past expiry.","commonSituations":"User leaves the provider consent page open for many minutes before clicking 'Allow'; browser restores an old tab with a stale callback URL; clock skew between servers making recently minted state appear expired.","solutions":["Restart the login flow to get a freshly signed state — expired state cannot be renewed.","If expirations happen routinely on slow networks, increase the state TTL when the state is minted.","Check NTP/ clock sync on the server if valid states are rejected immediately.","Ensure the user agent is not caching the callback URL across sessions."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const payload = decodeState(state);\nif (!payload || Date.now() / 1000 > payload.exp) {\n  restartLoginFlow(); // state already expired, do not call the API\n}","typeGuard":null,"tryCatchPattern":"try { await oauthCallback(state, code); } catch (e) { if (isTokenExpired(e)) redirectToLogin(); else throw e; }","preventionTips":["Treat expired state as 'start over', never as retryable with the same state","Keep the OAuth consent step prompt to reduce time-to-callback","Synchronize server clocks via NTP","Increase the state TTL only if slow user flows genuinely require it"],"tags":["auth","oauth","expired-token"],"backgroundTag":"oauth-state-expired","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}