{"record":{"id":"024e45fa1fb0c87e","repo":"gotify/server","slug":"groups-claim-q-is-missing","errorCode":null,"errorMessage":"groups claim %q is missing","messagePattern":"groups claim %q is missing","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"api/oidc.go","lineNumber":560,"sourceCode":"\treturn client, nil\n}\n\nfunc (a *OIDCAPI) popPendingSession(key string) (*pendingOIDCSession, bool) {\n\tsession, ok := a.pendingSessions.Pop(key)\n\tif ok && time.Since(session.CreatedAt) < pendingSessionMaxAge {\n\t\treturn session, true\n\t}\n\treturn nil, false\n}\n\nfunc (a *OIDCAPI) resolvePermission(idTokenClaims, userInfoClaims map[string]any) (bool, int, error) {\n\tif a.GroupsClaim == \"\" {\n\t\treturn false, 0, nil\n\t}\n\n\tgroupsRaw, ok := lookupClaim(a.GroupsClaim, idTokenClaims, userInfoClaims)\n\tif !ok {\n\t\treturn false, http.StatusInternalServerError, fmt.Errorf(\"groups claim %q is missing\", a.GroupsClaim)\n\t}\n\n\tvar groups []string\n\tswitch groupsRaw := groupsRaw.(type) {\n\tcase []string:\n\t\tgroups = groupsRaw\n\tcase []any:\n\t\tfor _, groupRaw := range groupsRaw {\n\t\t\tgroup, ok := groupRaw.(string)\n\t\t\tif !ok {\n\t\t\t\treturn false, http.StatusInternalServerError, fmt.Errorf(\"groups claim %q contains a non-string element: %#v\", a.GroupsClaim, groupRaw)\n\t\t\t}\n\t\t\tgroups = append(groups, group)\n\t\t}\n\tcase string:\n\t\tgroups = append(groups, groupsRaw)\n\tdefault:\n\t\treturn false, http.StatusInternalServerError, fmt.Errorf(\"groups claim %q is not a string or string array: %#v\", a.GroupsClaim, groupsRaw)","sourceCodeStart":542,"sourceCodeEnd":578,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/oidc.go#L542-L578","documentation":"resolvePermission reads the configured groups claim (a.GroupsClaim) from the ID token or userinfo to compute admin/user permissions. If the claim is absent from both sources it returns HTTP 500 with 'groups claim %q is missing'. Without the groups claim the library cannot map the identity to local roles when GroupsClaim is configured.","triggerScenarios":"GroupsClaim is set (e.g. 'groups') but neither idTokenClaims nor userInfoClaims contains it — mapper profile not enabled, client not assigned the groups mapper/protocol mapper, or scopes omit the claim.","commonSituations":"Keycloak: groups mapper not added to the client scope; Azure AD: group claims not configured in the app registration/token configuration; gitlab provider needing 'groups' scope; renaming the claim in the IdP without updating config.","solutions":["Configure the IdP to include the groups claim (Keycloak protocol mapper / Azure group claims)","Set GroupsClaim to the exact emitted claim name","Add required scopes/client scopes so the claim is issued","Verify by decoding a live token and checking the claim exists"],"exampleFix":"// before\nOIDC_GROUPS_CLAIM=memberOf\n// after (Keycloak default)\nOIDC_GROUPS_CLAIM=groups","handlingStrategy":"validation","validationCode":"// validate at startup with a decoded sample token\nvar claims map[string]any\njson.Unmarshal(sampleTokenPayload, &claims)\nif _, ok := claims[groupsClaim]; !ok {\n    log.Fatalf(\"groups claim %q absent from IdP tokens; configure mapper or scopes\", groupsClaim)\n}","typeGuard":"func hasGroupsClaim(claims map[string]any, name string) bool {\n    _, ok := claims[name]\n    return ok\n}","tryCatchPattern":"user, status, err := resolveUser(...)\nif err != nil && strings.Contains(err.Error(), \"groups claim\") {\n    // IdP token misconfiguration: enable group mapper / scopes\n    http.Error(w, \"role mapping misconfigured\", http.StatusInternalServerError)\n    return\n}","preventionTips":["Enable the groups protocol mapper (Keycloak) or group claims (Azure app registration)","Keep GroupsClaim config in sync with the IdP after any rename","Add required client scopes for groups","Smoke-test tokens with jwt.io after IdP changes"],"tags":["oidc","claims","authorization","configuration"],"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"}