gotify/server · error
username claim %q is missing
Error message
username claim %q is missing
What it means
After OIDC authentication succeeds but no user is matched, resolveUser extracts the username from the configured claim (a.UsernameClaim) in the ID token or userinfo. If lookupClaim cannot find that claim at all, it returns HTTP 500 with 'username claim %q is missing'. The library requires a non-empty username to map the OIDC identity to a local account.
Source
Thrown at api/oidc.go:467
if err != nil {
log.Err(err).Str("oidc_id", oidcID).Interface("idTokenClaims", idToken.Claims).Interface("userinfoClaims", info.Claims).Msg("OIDC: resolve permission")
return nil, status, err
}
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 {View on GitHub (pinned to 14bfc25627)
Solutions
- Set the username claim env/config to a claim the IdP actually emits (e.g. preferred_username, email, upn)
- Request the required scopes (openid profile email) on the OIDC client so the claim is populated
- Decode a real ID token (jwt.io) to confirm which claims are present
- Enable the userinfo endpoint if relying on info.Claims
Example fix
// before OIDC_USERNAME_CLAIM=upn // after OIDC_USERNAME_CLAIM=preferred_username
Defensive patterns
Strategy: validation
Validate before calling
// before configuring, decode a token and check the claim
claims := map[string]any{}
json.Unmarshal(tokenPayload, &claims)
if _, ok := claims["preferred_username"]; !ok {
log.Fatal("configured username claim missing from tokens")
} Type guard
func hasUsernameClaim(claims map[string]any, name string) (string, bool) {
v, ok := claims[name]
if !ok { return "", false }
s, ok := v.(string)
return s, ok && s != ""
} Try / catch
user, status, err := resolveUser(...)
if err != nil && strings.Contains(err.Error(), "username claim") {
// fix OIDC username-claim config or IdP scopes; 500 is server-side misconfig
http.Error(w, "identity mapping misconfigured", http.StatusInternalServerError)
return
} Prevention
- Request openid profile email scopes on the OIDC client
- Verify claim names against a decoded token after any IdP change
- Document the required claim per IdP (Keycloak/Azure/ADFS differ)
- Add a startup self-test that validates a sample token's claims
When it happens
Trigger: The configured UsernameClaim name does not exist in idToken.Claims or the userinfo claims — e.g. OIDC_USERNAME_CLAIM set to 'preferred_username' while the IdP issues 'email', or 'upn' for Azure AD tokens that lack it.
Common situations: Switching IdPs (Keycloak -> Azure AD/Entra) changes claim names; client scopes (profile/email) not requested so preferred_username is absent; typo in the claim config key; userinfo endpoint disabled while the token lacks the claim.
Related errors
- issuer claim was empty
- subject claim was empty
- groups claim %q is missing
- username claim was empty
- user does not exist and auto-registration is disabled
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/61d2db3b3ef1c55d.
Report an issue: GitHub.