{"record":{"id":"19246257f2559296","repo":"cloudflare/cloudflared","slug":"aud-field-is-not-a-string-or-an-array-of-strings","errorCode":null,"errorMessage":"aud field is not a string or an array of strings","messagePattern":"aud field is not a string or an array of strings","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/token.go","lineNumber":96,"sourceCode":"\tvar audParser struct {\n\t\tAud any `json:\"aud\"`\n\t}\n\tif err := json.Unmarshal(data, &audParser); err != nil {\n\t\treturn err\n\t}\n\tswitch aud := audParser.Aud.(type) {\n\tcase string:\n\t\tp.Aud = []string{aud}\n\tcase []any:\n\t\tfor _, a := range aud {\n\t\t\ts, ok := a.(string)\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"aud array contains non-string elements\")\n\t\t\t}\n\t\t\tp.Aud = append(p.Aud, s)\n\t\t}\n\tdefault:\n\t\treturn errors.New(\"aud field is not a string or an array of strings\")\n\t}\n\treturn nil\n}\n\nfunc (p jwtPayload) isExpired() bool {\n\treturn int(time.Now().Unix()) > p.Exp\n}\n\nconst (\n\tlockRetryInterval  = 2 * time.Second\n\tlockTimeout        = 10 * time.Minute\n\tstartTimeTolerance = int64(1000) // milliseconds\n)\n\n// acquireLockFile loops until it successfully creates a lock file for the\n// given token file path. The lock file is created at tokenPath + \".lock\".\n//\n// On each iteration:","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/token.go#L78-L114","documentation":"jwtPayload.UnmarshalJSON requires the \"aud\" claim to be either a JSON string or an array of strings. Any other JSON type (number, object, boolean, null) hits the default branch and returns this error, rejecting the token as structurally invalid.","triggerScenarios":"Unmarshalling a JWT payload whose aud claim is not a string or string-array, e.g. \"aud\": 12345, \"aud\": {\"tenant\":\"x\"}, or \"aud\": true.","commonSituations":"Custom token minters that serialize audience as a numeric client ID or nested object, tokens issued by non-standard services, or manually edited/hand-rolled JWTs used in testing.","solutions":["Fix the token issuer to emit aud as a string or an array of strings, e.g. \"aud\": \"app-id\" or [\"app-id\"].","Decode the token payload (base64 JSON) and confirm the aud claim's JSON type before debugging further.","If the issuer is fixed but cached tokens persist, discard/regenerate the cached tokens."],"exampleFix":"// before: {\"aud\": 12345}\n// after: {\"aud\": [\"12345\"]}","handlingStrategy":"validation","validationCode":"switch payloadMap[\"aud\"].(type) {\ncase string, []any:\n    // acceptable shape (array still needs string-element check)\ndefault:\n    return fmt.Errorf(\"aud must be a string or array of strings, got %T\", payloadMap[\"aud\"])\n}","typeGuard":"func hasValidAudShape(v any) bool {\n    switch t := v.(type) {\n    case string:\n        return t != \"\"\n    case []any:\n        return len(t) > 0\n    default:\n        return false\n    }\n}","tryCatchPattern":"p := jwtPayload{}\nif err := json.Unmarshal(rawPayload, &p); err != nil {\n    if strings.Contains(err.Error(), \"aud field\") {\n        return fmt.Errorf(\"token rejected: aud claim must be string or []string: %w\", err)\n    }\n    return err\n}","preventionTips":["Standardize token minting so aud is always a string or []string claim.","Never hand-edit JWT payloads; regenerate tokens through the issuing library.","Validate the payload JSON shape in tests before shipping a new token producer."],"tags":["jwt","validation","audience-claim","token"],"backgroundTag":"schema-validation-failed","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}