{"record":{"id":"01a67513ff014b48","repo":"Tencent/WeKnora","slug":"w-w","errorCode":null,"errorMessage":"%w: %w","messagePattern":"%w: %w","errorType":"validation","errorClass":"errInvalidExternalUserToken","httpStatus":401,"severity":"error","filePath":"internal/middleware/auth.go","lineNumber":597,"sourceCode":"\t\texternalUserID := strings.TrimSpace(header.Get(defaultExternalUserIDHeader))\n\t\tif externalUserID == \"\" {\n\t\t\tif cfg.RequireDirectHeader {\n\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\")","sourceCodeStart":579,"sourceCodeEnd":615,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/middleware/auth.go#L579-L615","documentation":"In internal/middleware/auth.go:597, resolveAPIPrincipal (APIPrincipalModeSignedToken) wraps errInvalidExternalUserToken with the JWT verification failure from verifyExternalUserJWT. The signed external-user token is missing, malformed, fails signature/audience/expiration checks, or verifies to an empty subject, so the principal cannot be resolved.","triggerScenarios":"Tenant configured with APIPrincipalModeSignedToken; the external user token header (defaultExternalUserTokenHeader) is missing, the HS256 HMAC signature doesn't match cfg.HMACSecret, audience isn't \"weknora\", token expired/no exp claim, or sub claim is empty.","commonSituations":"Token signed with a different secret after rotating HMACSecret; wrong audience claim; client sending the raw API key instead of the JWT; clock skew making a just-issued token appear expired; token issued for a different workspace.","solutions":["Re-issue the JWT signed with the tenant's current cfg.HMACSecret using HS256, aud=\"weknora\", valid exp and non-empty sub.","Confirm the client sends the token in the correct external user token header.","Check server/client clock synchronization if tokens fail right after issuance.","Use errors.Is(err, errInvalidExternalUserToken) to detect this family and log the wrapped cause."],"exampleFix":"// before: token signed with old secret after rotation\ntok := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{\"aud\": \"weknora\", \"exp\": ..., \"sub\": uid})\ns, _ := tok.SignedString([]byte(oldSecret))\n// after\ns, _ := tok.SignedString([]byte(tenant.APIPrincipalConfig.HMACSecret))","handlingStrategy":"try-catch","validationCode":"if strings.TrimSpace(tokenHeader) == \"\" { // ensure token present before calling\n    // obtain a fresh signed token first\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if errors.Is(err, errInvalidExternalUserToken) {\n        // mint a new HS256 token (aud=weknora, exp<=TTL, sub set) and retry\n    }\n}","preventionTips":["Keep token signing secret in sync with tenant APIPrincipalConfig.HMACSecret.","Set aud=\"weknora\", exp within TTL, and non-empty sub at issuance.","Refresh tokens proactively before expiry; monitor server-side Warnf logs."],"tags":["authentication","jwt","hmac"],"backgroundTag":"invalid-external-user-token","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}