gotify/server · error
username claim was empty
Error message
username claim was empty
What it means
resolveUser throws 500 'username claim was empty' when the configured username claim (a.UsernameClaim, default typically 'username' or preferred_username) is present but its value is empty, while resolving an OIDC user (during username-based linking).
Source
Thrown at api/oidc.go:471
if user != nil {
if len(a.GroupsAdmin) > 0 && user.Admin != hasAdminGroup {
user.Admin = hasAdminGroup
if err := a.DB.UpdateUser(user); err != nil {
return nil, http.StatusInternalServerError, fmt.Errorf("database error: %w", err)
}
log.Warn().Str("oidc_id", oidcID).Str("username", user.Name).Bool("admin", user.Admin).Msg("OIDC change permission")
}
return user, 0, nil
}
usernameRaw, ok := lookupClaim(a.UsernameClaim, idToken.Claims, info.Claims)
if !ok {
return nil, http.StatusInternalServerError, fmt.Errorf("username claim %q is missing", a.UsernameClaim)
}
username := fmt.Sprint(usernameRaw)
if username == "" || usernameRaw == nil {
return nil, http.StatusInternalServerError, errors.New("username claim was empty")
}
byUsername, err := a.DB.GetUserByName(username)
if err != nil {
return nil, http.StatusInternalServerError, fmt.Errorf("database error: %w", err)
}
if byUsername != nil {
return a.linkExistingUser(byUsername, oidcID, hasAdminGroup)
}
return a.registerUser(username, oidcID, hasAdminGroup)
}
func (a *OIDCAPI) linkExistingUser(user *model.User, oidcID string, hasAdminGroup bool) (*model.User, int, error) {
if !a.LinkByUsername {
log.Warn().Str("oidc_id", oidcID).Str("username", user.Name).Msgf("OIDC login rejected: a local user with the username already exists and %s is disabled", config.EnvOIDCLinkByUsername)
return nil, http.StatusForbidden, fmt.Errorf("a local user with the username %s already exists and linking by username is disabled", user.Name)
}
if user.OIDCID != nil {View on GitHub (pinned to 14bfc25627)
Solutions
- Set GOTIFY_OIDC_USERNAME_CLAIM to a claim your provider actually populates (e.g. preferred_username, email).
- Fix the user's profile at the IdP so the claim has a value.
- Decode the ID token and confirm the claim key and value.
- Check for case-sensitivity/typos in the claim name.
Example fix
// before GOTIFY_OIDC_USERNAME_CLAIM=login // provider has no 'login' claim value // after GOTIFY_OIDC_USERNAME_CLAIM=preferred_username
Defensive patterns
Strategy: validation
Validate before calling
const claims = decodeJwt(idToken);
const raw = claims[process.env.GOTIFY_OIDC_USERNAME_CLAIM ?? 'preferred_username'];
if (raw == null || String(raw) === '') throw new Error('username claim empty; fix GOTIFY_OIDC_USERNAME_CLAIM or IdP mapping'); Type guard
function hasNonEmptyClaim(claims, name) { const v = claims[name]; return v != null && String(v) !== ''; } Prevention
- Verify the claim key exists and is non-empty in a real token.
- Choose a claim the IdP always populates (e.g. preferred_username, email).
- Populate user profiles at the IdP.
- Re-check config after IdP upgrades/claim renames.
When it happens
Trigger: resolveUser falls back to linking by username (user has no existing OIDC binding and GOTIFY_OIDC_LINK_BY_USERNAME is enabled), and the claim named by GOTIFY_OIDC_USERNAME_CLAIM exists in the token but resolves to "" or null.
Common situations: Renamed GOTIFY_OIDC_USERNAME_CLAIM to a claim the provider never populates; provider returns preferred_username as null for federated users; misconfigured claim transformation at the IdP.
Related errors
- issuer claim was empty
- subject claim was empty
- username claim %q is missing
- failed to bind user to OIDC identity: %w
- failed to create user: %w
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/e6ae8155876265a6.
Report an issue: GitHub.