Tencent/WeKnora · error
empty external user id
Error message
empty external user id
What it means
validateExternalUserID performs post-JWT sanity checks on the external user identifier extracted from the token. It rejects an empty (after trimming) ID because a principal without an ID cannot be persisted or authorized. This guards against tokens whose sub passed a whitespace-only trim earlier or IDs supplied through alternate lookup paths.
Source
Thrown at internal/middleware/auth.go:662
}
if nbf, nbfErr := claims.GetNotBefore(); nbfErr == nil && nbf != nil && time.Now().Before(nbf.Time) {
return "", errors.New("token not yet valid")
}
if got := principalTenantIDFromClaims(claims); got != tenantID {
return "", fmt.Errorf("workspace mismatch: got %d want %d", got, tenantID)
}
sub, _ := claims["sub"].(string)
sub = strings.TrimSpace(sub)
if sub == "" {
return "", errors.New("missing subject")
}
return sub, nil
}
func validateExternalUserID(id string) error {
id = strings.TrimSpace(id)
if id == "" {
return errors.New("empty external user id")
}
if len(id) > maxExternalUserIDLen {
return fmt.Errorf("external user id too long (max %d)", maxExternalUserIDLen)
}
for _, r := range id {
if r < 0x20 || r == 0x7f {
return errors.New("external user id contains invalid characters")
}
}
return nil
}
func apiPrincipalAuthErrorMessage(err error) string {
switch {
case errors.Is(err, errMissingDirectHeader):
return "Unauthorized: missing external user id header"
case errors.Is(err, errInvalidExternalUserID):
return "Unauthorized: invalid external user id"View on GitHub (pinned to 988cbb0330)
Solutions
- Ensure the upstream token/parameter actually carries the external user ID before calling the API.
- If the ID comes from a request field, add client-side/earlier middleware validation for non-empty values.
- Check the external user provisioning flow: the account must be created with a real external identifier.
- If calling validateExternalUserID directly in tests/tools, pass a real trimmed ID string.
Example fix
// before
if err := validateExternalUserID(c.GetHeader("X-External-User-Id")); err != nil { ... }
// after: check presence first
extID := strings.TrimSpace(c.GetHeader("X-External-User-Id"))
if extID == "" {
c.AbortWithStatusJSON(400, gin.H{"error": "external user id header required"})
return
}
if err := validateExternalUserID(extID); err != nil { ... } Defensive patterns
Strategy: validation
Validate before calling
func preValidateExternalUserID(id string) error {
if strings.TrimSpace(id) == "" {
return errors.New("external user id required")
}
return nil
}
// call before issuing the API request Type guard
func hasExternalUserID(v string) bool { return strings.TrimSpace(v) != "" } Prevention
- Always trim and check IDs at the client boundary before sending.
- Ensure user provisioning always sets a real external identifier.
- Add a not-null/length constraint on the external ID column in the user store.
When it happens
Trigger: resolveAPIPrincipal calling validateExternalUserID with an ID that is empty after TrimSpace — e.g. sub claim containing only spaces, or an empty ID passed when resolving a principal by explicit external user ID.
Common situations: IdP that issues blank sub values; callers extracting the ID from a header/query param that was not sent; data migration bugs where user records lack the external ID field.
Related errors
- missing subject
- external user id contains invalid characters
- invite code has expired
- join request not found
- failed to retrieve: %s
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/c3585b9b4b2c72ec.
Report an issue: GitHub.