gotify/server · warning

user is not in any allowed group

Error message

user is not in any allowed group

What it means

resolvePermission throws 403 'user is not in any allowed group' when the OIDC user's group/role claims match none of the configured allowed groups: not in GOTIFY_OIDC_GROUPS_ADMIN and not in GOTIFY_OIDC_GROUPS_USER (unless the user-group list is empty, which permits everyone).

Source

Thrown at api/oidc.go:587

			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)
	}

	switch {
	case containsAny(a.GroupsAdmin, groups):
		return true, 0, nil
	case len(a.GroupsUser) == 0 || containsAny(a.GroupsUser, groups):
		return false, 0, nil
	default:
		return false, http.StatusForbidden, errors.New("user is not in any allowed group")
	}
}

func lookupClaim(name string, idTokenClaims, userInfoClaims map[string]any) (any, bool) {
	if value, ok := idTokenClaims[name]; ok {
		return value, true
	}
	value, ok := userInfoClaims[name]
	return value, ok
}

func containsAny(configured, actual []string) bool {
	for _, value := range actual {
		if slices.Contains(configured, value) {
			return true
		}
	}

View on GitHub (pinned to 14bfc25627)

Solutions

  1. Update GOTIFY_OIDC_GROUPS_USER to include the group names your provider actually issues.
  2. Ensure the token includes a groups claim (configure the groups scope / claim mapping at the IdP).
  3. Clear GOTIFY_OIDC_GROUPS_USER to allow any authenticated OIDC user as a normal user.
  4. Match exact group names (case/whitespace) between IdP and Gotify config.

Example fix

// before
GOTIFY_OIDC_GROUPS_USER=gotify-users // IdP emits 'users'
// after
GOTIFY_OIDC_GROUPS_USER=gotify-users,users
Defensive patterns

Strategy: validation

Validate before calling

const groups = decodeJwt(idToken).groups ?? [];
const allowed = (process.env.GOTIFY_OIDC_GROUPS_USER ?? '').split(',').filter(Boolean);
if (allowed.length > 0 && !groups.some(g => allowed.includes(g))) throw new Error('token groups do not match GOTIFY_OIDC_GROUPS_USER');

Type guard

function inAnyGroup(tokenGroups, allowed) { return Array.isArray(tokenGroups) && tokenGroups.some(g => allowed.includes(g)); }

Prevention

When it happens

Trigger: resolveUser calls resolvePermission with the token's groups; the groups claim is present but contains none of the configured admin/user groups, or the groups claim itself is missing/empty so containsAny never matches while a user-group allowlist is configured.

Common situations: IdP emits groups under a different claim name (e.g. 'roles') than Gotify expects; group names renamed at the provider; client not granted the groups scope; typo in GOTIFY_OIDC_GROUPS_USER values.

Related errors


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