netbirdio/netbird · error

access token response returned code: %s

Error message

access token response returned code: %s

What it means

Raised by DeviceAuthorizationFlow.requestToken when the IdP token endpoint answers with a status code above 499 (device_flow.go:236-238). The raw response body is embedded in the error text, so the message typically contains the IdP's HTML or JSON error payload. It signals a server-side failure (or a middlebox answering 5xx) during the device-flow token poll, as opposed to a protocol-level OAuth error, which arrives as 200/4xx with an error JSON body.

Source

Thrown at client/internal/auth/device_flow.go:237

	res, err := d.HTTPClient.Do(req)
	if err != nil {
		return TokenRequestResponse{}, fmt.Errorf("failed to request access token with error: %v", err)
	}

	defer func() {
		err := res.Body.Close()
		if err != nil {
			return
		}
	}()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return TokenRequestResponse{}, fmt.Errorf("failed reading access token response body with error: %v", err)
	}

	if res.StatusCode > 499 {
		return TokenRequestResponse{}, fmt.Errorf("access token response returned code: %s", string(body))
	}

	tokenResponse := TokenRequestResponse{}
	err = json.Unmarshal(body, &tokenResponse)
	if err != nil {
		return TokenRequestResponse{}, fmt.Errorf("parsing token response failed with error: %v", err)
	}

	return tokenResponse, nil
}

// WaitToken waits user's login and authorize the app. Once the user's authorize
// it retrieves the access token from Hosted's endpoint and validates it before returning.
// The method creates a timeout context internally based on info.ExpiresIn.
func (d *DeviceAuthorizationFlow) WaitToken(ctx context.Context, info AuthFlowInfo) (TokenInfo, error) {
	// Create timeout context based on flow expiration
	timeout := time.Duration(info.ExpiresIn) * time.Second
	waitCtx, cancel := context.WithTimeout(ctx, timeout)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Read the embedded body in the error message - it usually names the failing upstream component
  2. Retry netbird up after a short wait; 5xx bursts during IdP deploys are usually transient
  3. Check the IdP status page and the reverse-proxy logs in front of the token endpoint
  4. If persistent, verify the TokenEndpoint configured on the management side points to the correct, healthy IdP URL
Defensive patterns

Strategy: retry

Try / catch

token, err := flow.WaitToken(ctx, info)
if err != nil && strings.Contains(err.Error(), "access token response returned code:") {
	// IdP/middlebox 5xx: wait and restart the flow rather than reporting a config problem
	log.Warnf("IdP 5xx during token poll: %v", err)
	time.Sleep(30 * time.Second)
	return retryLogin(ctx)
}

Prevention

When it happens

Trigger: The token POST (client_id + device_code + grant_type) returns 500/502/503/504. Typical producers: IdP outage or maintenance, IdP gateway rate-limiting the polling, a reverse proxy in front of the IdP returning 502/504, or an auth service bug. Note this fires during WaitToken polling, so it can appear after the user already opened the verification URI.

Common situations: Auth0/Keycloak/Zitadel incidents or deploys, misconfigured reverse proxy (nginx/traefik) in front of the IdP timing out upstream, user polling during IdP maintenance windows.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/477d3db59544934a. Report an issue: GitHub.