{"record":{"id":"e6ae8155876265a6","repo":"gotify/server","slug":"username-claim-was-empty","errorCode":null,"errorMessage":"username claim was empty","messagePattern":"username claim was empty","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"api/oidc.go","lineNumber":471,"sourceCode":"\n\tif user != nil {\n\t\tif len(a.GroupsAdmin) > 0 && user.Admin != hasAdminGroup {\n\t\t\tuser.Admin = hasAdminGroup\n\t\t\tif err := a.DB.UpdateUser(user); err != nil {\n\t\t\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"database error: %w\", err)\n\t\t\t}\n\t\t\tlog.Warn().Str(\"oidc_id\", oidcID).Str(\"username\", user.Name).Bool(\"admin\", user.Admin).Msg(\"OIDC change permission\")\n\t\t}\n\t\treturn user, 0, nil\n\t}\n\n\tusernameRaw, ok := lookupClaim(a.UsernameClaim, idToken.Claims, info.Claims)\n\tif !ok {\n\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"username claim %q is missing\", a.UsernameClaim)\n\t}\n\tusername := fmt.Sprint(usernameRaw)\n\tif username == \"\" || usernameRaw == nil {\n\t\treturn nil, http.StatusInternalServerError, errors.New(\"username claim was empty\")\n\t}\n\n\tbyUsername, err := a.DB.GetUserByName(username)\n\tif err != nil {\n\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"database error: %w\", err)\n\t}\n\tif byUsername != nil {\n\t\treturn a.linkExistingUser(byUsername, oidcID, hasAdminGroup)\n\t}\n\treturn a.registerUser(username, oidcID, hasAdminGroup)\n}\n\nfunc (a *OIDCAPI) linkExistingUser(user *model.User, oidcID string, hasAdminGroup bool) (*model.User, int, error) {\n\tif !a.LinkByUsername {\n\t\tlog.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)\n\t\treturn nil, http.StatusForbidden, fmt.Errorf(\"a local user with the username %s already exists and linking by username is disabled\", user.Name)\n\t}\n\tif user.OIDCID != nil {","sourceCodeStart":453,"sourceCodeEnd":489,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/oidc.go#L453-L489","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nGOTIFY_OIDC_USERNAME_CLAIM=login // provider has no 'login' claim value\n// after\nGOTIFY_OIDC_USERNAME_CLAIM=preferred_username","handlingStrategy":"validation","validationCode":"const claims = decodeJwt(idToken);\nconst raw = claims[process.env.GOTIFY_OIDC_USERNAME_CLAIM ?? 'preferred_username'];\nif (raw == null || String(raw) === '') throw new Error('username claim empty; fix GOTIFY_OIDC_USERNAME_CLAIM or IdP mapping');","typeGuard":"function hasNonEmptyClaim(claims, name) { const v = claims[name]; return v != null && String(v) !== ''; }","tryCatchPattern":null,"preventionTips":["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."],"tags":["oidc","claims","http-500","config"],"backgroundTag":"jwt-missing-claim","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}