netbirdio/netbird · error
authentication failed: Invalid state
Error message
authentication failed: Invalid state
What it means
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.
Source
Thrown at client/internal/auth/pkce_flow.go:269
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errChan <- err
}
}
func (p *PKCEAuthorizationFlow) handleRequest(req *http.Request) (*oauth2.Token, error) {
query := req.URL.Query()
if authError := query.Get(queryError); authError != "" {
authErrorDesc := query.Get(queryErrorDesc)
if authErrorDesc != "" {
return nil, fmt.Errorf("authentication failed: %s", authErrorDesc)
}
return nil, fmt.Errorf("authentication failed: %s", authError)
}
// Prevent timing attacks on the state
if state := query.Get(queryState); subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {
return nil, fmt.Errorf("authentication failed: Invalid state")
}
code := query.Get(queryCode)
if code == "" {
return nil, fmt.Errorf("authentication failed: missing code")
}
exchangeStart := time.Now()
token, err := p.oAuthConfig.Exchange(
req.Context(),
code,
oauth2.SetAuthURLParam("code_verifier", p.codeVerifier),
)
if err != nil {
return nil, err
}
log.Infof("pkce flow: authorization code exchanged for token in %s", time.Since(exchangeStart).Round(time.Millisecond))View on GitHub (pinned to 93e97f4bf1)
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.
Defensive patterns
Strategy: try-catch
Try / catch
_, err := flow.WaitToken(ctx, info)
if err != nil {
if strings.Contains(err.Error(), "Invalid state") {
// stale or forged callback: restart the flow with RequestAuthInfo
// instead of retrying the callback URL
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- PKCE authorization flow failed: %v
- %s %s
- authentication failed: %s
- authentication failed: missing code
- client already started
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/c5180ecac21baa32.
Report an issue: GitHub.