{"record":{"id":"f3d86a37240ea48b","repo":"netbirdio/netbird","slug":"token-is-not-a-well-formed-jwt","errorCode":null,"errorMessage":"token is not a well-formed JWT","messagePattern":"token is not a well-formed JWT","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/internal/auth/util.go","lineNumber":39,"sourceCode":"}\n\n// validateTokenAudience checks that the token is a well-formed JWT whose\n// audience claim matches the expected audience.\n//\n// It does NOT verify the token's cryptographic signature and therefore must not\n// be treated as an authenticity check. The token is obtained by the client\n// directly from the IdP token endpoint over TLS, and its signature is verified\n// server-side by the management server against the IdP's JWKS\n// (see shared/auth/jwt/validator.go). This function is only a client-side\n// sanity check that the returned token targets the expected audience.\nfunc validateTokenAudience(token string, audience string) error {\n\tif token == \"\" {\n\t\treturn fmt.Errorf(\"token received is empty\")\n\t}\n\n\tparts := strings.Split(token, \".\")\n\tif len(parts) != 3 {\n\t\treturn fmt.Errorf(\"token is not a well-formed JWT\")\n\t}\n\n\tclaimsString, err := base64.RawURLEncoding.DecodeString(parts[1])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclaims := Claims{}\n\terr = json.Unmarshal(claimsString, &claims)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif claims.Audience == nil {\n\t\treturn fmt.Errorf(\"required token field audience is absent\")\n\t}\n\n\t// Audience claim of JWT can be a string or an array of strings","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/internal/auth/util.go#L21-L57","documentation":"validateTokenAudience split the token on '.' and did not get exactly three parts, so the token is not a structurally valid JWT. Many IdPs can issue opaque (non-JWT) access tokens; those can never pass this check because their payload is not base64url-encoded JSON claims.","triggerScenarios":"strings.Split(token, \".\") yields a length other than 3: the access token is an opaque random string (common for Azure AD v1-style or custom-issued tokens), a Key Reference/token-exchange artifact, or the wrong token type entirely (refresh token or a SAML assertion) ended up in the field.","commonSituations":"Azure AD issuing opaque tokens for certain resource configurations; opaque tokens; provider returning a reference token meant only for its introspection endpoint.","solutions":["Enable 'Use ID Token' in the NetBird IdP configuration - the id_token is always a JWT and passes the structural check.","Alternatively, reconfigure the IdP application to issue JWT access tokens (for example register the API so the token carries the aud claim as a JWT).","Decode a sample token manually: if it has no two dots and no base64url payload, it is opaque and the configuration must change.","Re-login after changing the IdP configuration so a fresh token is validated."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// cheap structural check a caller can apply to a token string\nfunc looksLikeJWT(t string) bool {\n    parts := strings.Split(t, \".\")\n    if len(parts) != 3 {\n        return false\n    }\n    _, err := base64.RawURLEncoding.DecodeString(parts[1])\n    return err == nil\n}","typeGuard":"func isJWT(s string) bool { return len(strings.Split(s, \".\")) == 3 }","tryCatchPattern":"if err := validateTokenAudience(token, audience); err != nil {\n    if strings.Contains(err.Error(), \"not a well-formed JWT\") {\n        // opaque access token: enable Use ID Token or configure the IdP to\n        // issue JWT access tokens; retrying cannot help\n    }\n}","preventionTips":["Register the API in the IdP so access tokens are JWTs with an audience.","Prefer id_token for client-side validation when the provider is opaque-token-only.","Smoke-test one issued token per environment with a decoder before rollout."],"tags":["oauth2","jwt","idp","opaque-token"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}