{"record":{"id":"c5180ecac21baa32","repo":"netbirdio/netbird","slug":"authentication-failed-invalid-state","errorCode":null,"errorMessage":"authentication failed: Invalid state","messagePattern":"authentication failed: Invalid state","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/internal/auth/pkce_flow.go","lineNumber":269,"sourceCode":"\tif err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\terrChan <- err\n\t}\n}\n\nfunc (p *PKCEAuthorizationFlow) handleRequest(req *http.Request) (*oauth2.Token, error) {\n\tquery := req.URL.Query()\n\n\tif authError := query.Get(queryError); authError != \"\" {\n\t\tauthErrorDesc := query.Get(queryErrorDesc)\n\t\tif authErrorDesc != \"\" {\n\t\t\treturn nil, fmt.Errorf(\"authentication failed: %s\", authErrorDesc)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"authentication failed: %s\", authError)\n\t}\n\n\t// Prevent timing attacks on the state\n\tif state := query.Get(queryState); subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {\n\t\treturn nil, fmt.Errorf(\"authentication failed: Invalid state\")\n\t}\n\n\tcode := query.Get(queryCode)\n\tif code == \"\" {\n\t\treturn nil, fmt.Errorf(\"authentication failed: missing code\")\n\t}\n\n\texchangeStart := time.Now()\n\ttoken, err := p.oAuthConfig.Exchange(\n\t\treq.Context(),\n\t\tcode,\n\t\toauth2.SetAuthURLParam(\"code_verifier\", p.codeVerifier),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Infof(\"pkce flow: authorization code exchanged for token in %s\", time.Since(exchangeStart).Round(time.Millisecond))","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/internal/auth/pkce_flow.go#L251-L287","documentation":"The state query parameter on the IdP callback did not match the state generated at flow start (subtle.ConstantTimeCompare failed). The state is 24 random bytes in hex binding this browser round-trip to this flow instance; a mismatch means the callback belongs to a different flow, a stale one, or is forged. The constant-time compare prevents leaking how many bytes matched.","triggerScenarios":"A callback from a previous login attempt arrives after a newer RequestAuthInfo overwrote p.state; two login flows run concurrently on the same machine; the callback URL is reloaded (browser refresh, prefetch, or a duplicate tab) replaying the old state; a crafted request hits the local server with a wrong or missing state.","commonSituations":"User refreshes the 'login successful' page, re-sending the old query; user runs netbird up twice and completes the first tab after the second flow armed a new state; browser extensions prefetching the redirect URL; bookmarked or manually re-opened localhost callback URLs.","solutions":["Close all stale NetBird login tabs and retry the login to start a flow with a fresh state.","Run only one login flow at a time - a second flow invalidates the first flow's state.","Do not refresh or bookmark the localhost callback page; complete the flow once and let the browser land on the rendered success template.","If it persists, compare the state parameter in the failing callback URL against the state in the authorization URL the client opened."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"_, err := flow.WaitToken(ctx, info)\nif err != nil {\n    if strings.Contains(err.Error(), \"Invalid state\") {\n        // stale or forged callback: restart the flow with RequestAuthInfo\n        // instead of retrying the callback URL\n    }\n}","preventionTips":["Never run two login flows concurrently on one machine; the second overwrites state and code_verifier.","Close old NetBird login tabs before starting a new login.","Do not refresh the callback page or re-open it from history.","Treat repeated state mismatches as a red flag: verify no local process is probing the callback port."],"tags":["oauth2","state","csrf","pkce","authentication"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}