{"record":{"id":"89381c2ca102d47a","repo":"Tencent/WeKnora","slug":"invalid-external-user-id-v","errorCode":null,"errorMessage":"invalid external user id: %v","messagePattern":"invalid external user id: (.+?)","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"internal/middleware/auth.go","lineNumber":600,"sourceCode":"\t\t\t\treturn types.Principal{}, errMissingDirectHeader\n\t\t\t}\n\t\t\treturn fallback, nil\n\t\t}\n\t\tif err := validateExternalUserID(externalUserID); err != nil {\n\t\t\treturn types.Principal{}, fmt.Errorf(\"%w: %v\", errInvalidExternalUserID, err)\n\t\t}\n\t\treturn types.Principal{\n\t\t\tType: types.PrincipalAPIExternalUser,\n\t\t\tID:   strconv.FormatUint(tenantID, 10) + \":\" + externalUserID,\n\t\t}, nil\n\tcase types.APIPrincipalModeSignedToken:\n\t\texternalUserID, err := verifyExternalUserJWT(header.Get(defaultExternalUserTokenHeader), tenantID, cfg.HMACSecret)\n\t\tif err != nil || externalUserID == \"\" {\n\t\t\tlogger.Warnf(ctx, \"invalid external user token for tenant=%d: %v\", tenantID, err)\n\t\t\treturn types.Principal{}, fmt.Errorf(\"%w: %w\", errInvalidExternalUserToken, err)\n\t\t}\n\t\tif err := validateExternalUserID(externalUserID); err != nil {\n\t\t\treturn types.Principal{}, fmt.Errorf(\"%w: %v\", errInvalidExternalUserID, err)\n\t\t}\n\t\treturn types.Principal{\n\t\t\tType: types.PrincipalAPIExternalUser,\n\t\t\tID:   strconv.FormatUint(tenantID, 10) + \":\" + externalUserID,\n\t\t}, nil\n\tdefault:\n\t\treturn fallback, nil\n\t}\n}\n\nfunc verifyExternalUserJWT(tokenString string, tenantID uint64, secret string) (string, error) {\n\ttokenString = strings.TrimSpace(tokenString)\n\tsecret = strings.TrimSpace(secret)\n\tif tokenString == \"\" {\n\t\treturn \"\", errors.New(\"missing external user token\")\n\t}\n\tif secret == \"\" {\n\t\treturn \"\", errors.New(\"external user token secret is not configured\")","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/middleware/auth.go#L582-L618","documentation":"In internal/middleware/auth.go:600, resolveAPIPrincipal wraps errInvalidExternalUserID with the validateExternalUserID failure for the subject extracted from a verified signed token. Even though the JWT is cryptographically valid, its sub claim is empty, exceeds maxExternalUserIDLen, or contains control characters.","triggerScenarios":"APIPrincipalModeSignedToken tenant; verifyExternalUserJWT succeeds but claims[\"sub\"] trims to empty, is longer than maxExternalUserIDLen, or contains bytes <0x20 or 0x7f.","commonSituations":"Identity provider issues tokens without a sub claim; sub contains an embedded newline from a bad issuer integration; sub is a long composite string exceeding the length cap.","solutions":["Ensure the token issuer always sets a non-empty, short, printable sub claim.","Trim the sub on the issuing side and validate its length before signing.","If the IDP cannot provide sub, switch the tenant to APIPrincipalModeTenant to skip external-user resolution."],"exampleFix":"// before: token without sub\nclaims := jwt.MapClaims{\"aud\": \"weknora\", \"exp\": ...}\n// after\nclaims := jwt.MapClaims{\"aud\": \"weknora\", \"exp\": ..., \"sub\": externalUserID}","handlingStrategy":"validation","validationCode":"sub, _ := claims[\"sub\"].(string)\nsub = strings.TrimSpace(sub)\nif sub == \"\" || len(sub) > maxExternalUserIDLen { // fix at issuance\n}","typeGuard":"func hasValidSub(claims jwt.MapClaims) bool {\n    sub, ok := claims[\"sub\"].(string)\n    sub = strings.TrimSpace(sub)\n    return ok && sub != \"\" && len(sub) <= maxExternalUserIDLen\n}","tryCatchPattern":null,"preventionTips":["Always set sub when minting external-user tokens.","Validate sub length/characters at your token issuer.","Test tokens against validateExternalUserID rules in CI."],"tags":["authentication","jwt","validation"],"backgroundTag":"invalid-external-user-id","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}