gotify/server · error

groups claim %q is missing

Error message

groups claim %q is missing

What it means

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.

Source

Thrown at api/oidc.go:560

	return client, nil
}

func (a *OIDCAPI) popPendingSession(key string) (*pendingOIDCSession, bool) {
	session, ok := a.pendingSessions.Pop(key)
	if ok && time.Since(session.CreatedAt) < pendingSessionMaxAge {
		return session, true
	}
	return nil, false
}

func (a *OIDCAPI) resolvePermission(idTokenClaims, userInfoClaims map[string]any) (bool, int, error) {
	if a.GroupsClaim == "" {
		return false, 0, nil
	}

	groupsRaw, ok := lookupClaim(a.GroupsClaim, idTokenClaims, userInfoClaims)
	if !ok {
		return false, http.StatusInternalServerError, fmt.Errorf("groups claim %q is missing", a.GroupsClaim)
	}

	var groups []string
	switch groupsRaw := groupsRaw.(type) {
	case []string:
		groups = groupsRaw
	case []any:
		for _, groupRaw := range groupsRaw {
			group, ok := groupRaw.(string)
			if !ok {
				return false, http.StatusInternalServerError, fmt.Errorf("groups claim %q contains a non-string element: %#v", a.GroupsClaim, groupRaw)
			}
			groups = append(groups, group)
		}
	case string:
		groups = append(groups, groupsRaw)
	default:
		return false, http.StatusInternalServerError, fmt.Errorf("groups claim %q is not a string or string array: %#v", a.GroupsClaim, groupsRaw)

View on GitHub (pinned to 14bfc25627)

Solutions

  1. Configure the IdP to include the groups claim (Keycloak protocol mapper / Azure group claims)
  2. Set GroupsClaim to the exact emitted claim name
  3. Add required scopes/client scopes so the claim is issued
  4. Verify by decoding a live token and checking the claim exists

Example fix

// before
OIDC_GROUPS_CLAIM=memberOf
// after (Keycloak default)
OIDC_GROUPS_CLAIM=groups
Defensive patterns

Strategy: validation

Validate before calling

// validate at startup with a decoded sample token
var claims map[string]any
json.Unmarshal(sampleTokenPayload, &claims)
if _, ok := claims[groupsClaim]; !ok {
    log.Fatalf("groups claim %q absent from IdP tokens; configure mapper or scopes", groupsClaim)
}

Type guard

func hasGroupsClaim(claims map[string]any, name string) bool {
    _, ok := claims[name]
    return ok
}

Try / catch

user, status, err := resolveUser(...)
if err != nil && strings.Contains(err.Error(), "groups claim") {
    // IdP token misconfiguration: enable group mapper / scopes
    http.Error(w, "role mapping misconfigured", http.StatusInternalServerError)
    return
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05). Data as JSON: /api/errors/024e45fa1fb0c87e. Report an issue: GitHub.