netbirdio/netbird · error
authentication failed: invalid access token - %w
Error message
authentication failed: invalid access token - %w
What it means
After a successful token exchange, the client-side audience sanity check (validateTokenAudience) rejected the token that would be used for management login. The wrapped error says which check failed: token empty, not a three-part JWT, missing aud claim, or an aud matching neither the configured Audience nor - when Audience is empty - the ClientID fallback. This is a configuration sanity check, not a signature verification (that happens server-side against the IdP JWKS).
Source
Thrown at client/internal/auth/pkce_flow.go:310
tokenInfo := TokenInfo{
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
TokenType: token.TokenType,
ExpiresIn: token.Expiry.Second(),
UseIDToken: p.providerConfig.UseIDToken,
}
if idToken, ok := token.Extra("id_token").(string); ok {
tokenInfo.IDToken = idToken
}
// if a provider doesn't support an audience, use the Client ID for token verification
audience := p.providerConfig.Audience
if audience == "" {
audience = p.providerConfig.ClientID
}
if err := validateTokenAudience(tokenInfo.GetTokenToUse(), audience); err != nil {
return TokenInfo{}, fmt.Errorf("authentication failed: invalid access token - %w", err)
}
email, err := parseEmailFromIDToken(tokenInfo.IDToken)
if err != nil {
log.Warnf("failed to parse email from ID token: %v", err)
} else {
tokenInfo.Email = email
}
return tokenInfo, nil
}
// parseEmailFromIDToken extracts the email (or name) claim from an ID token
// without verifying its signature. The value is best-effort and used only as a
// UX convenience (login hint prefill and display); it never drives an
// authorization decision. The authoritative identity is established server-side
// from the signature-verified token.
func parseEmailFromIDToken(token string) (string, error) {View on GitHub (pinned to 93e97f4bf1)
Solutions
- Decode the failing JWT (base64url-decode the middle dot-separated part) and compare its aud claim with the Audience configured in NetBird's IdP settings; make them match.
- If the provider has no audience concept, ensure the token's aud equals the ClientID - that is the fallback checked when Audience is empty.
- If the access token is opaque (not a JWT), enable 'Use ID Token' in the IdP configuration so the always-JWT id_token is validated instead.
- Re-login after the administrator fixes the IdP configuration; the check runs on every fresh token retrieval.
Defensive patterns
Strategy: validation
Validate before calling
// decode the aud claim of a sample token and compare with what NetBird will check
func tokenAudienceMatches(token, expected string) bool {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return false
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return false
}
var claims struct {
Audience json.RawMessage `json:"aud"`
}
if json.Unmarshal(payload, &claims) != nil || len(claims.Audience) == 0 {
return false
}
var auds []string
if json.Unmarshal(claims.Audience, &auds) != nil {
var single string
if json.Unmarshal(claims.Audience, &single) == nil {
auds = []string{single}
}
}
for _, a := range auds {
if a == expected {
return true
}
}
return false
} Type guard
func isJWT(s string) bool { return len(strings.Split(s, ".")) == 3 } Try / catch
tokenInfo, err := flow.WaitToken(ctx, info)
if err != nil {
if strings.Contains(err.Error(), "invalid access token") {
// configuration mismatch between IdP audience and NetBird settings;
// fix the Audience (or enable Use ID Token) before retrying
}
} Prevention
- Set NetBird's Audience to the IdP's exact API identifier before the first login.
- If the provider cannot issue JWT access tokens, enable Use ID Token in the IdP configuration.
- When Audience is left empty, confirm the token's aud equals the ClientID - that is what gets checked.
- After any IdP app registration change, decode a sample token and verify aud before rolling out.
When it happens
Trigger: parseOAuthToken calls validateTokenAudience(tokenInfo.GetTokenToUse(), audience): the IdP issued the token for a different API identifier than configured; the access token is opaque rather than a JWT; UseIDToken is set but no id_token came back so the checked string is empty; Audience is unset so ClientID is compared but the token's aud is a resource URI.
Common situations: Auth0/Azure AD API identifier (aud) differs from the Audience entered in NetBird's IdP settings; provider does not support the audience parameter so the ClientID fallback is used while the token still targets a resource scope; some IdP configurations issue opaque access tokens unless the app registration is adjusted; UseIDToken enabled on a flow where the scopes omit openid so no id_token is returned.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- validate access token failed with error: %v
- required token field audience is absent
- token received is empty
- token is not a well-formed JWT
- failed reading access token response body with error: %v
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/186cf8a3e7c54af6.
Report an issue: GitHub.