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
- Update GOTIFY_OIDC_GROUPS_USER to include the group names your provider actually issues.
- Ensure the token includes a groups claim (configure the groups scope / claim mapping at the IdP).
- Clear GOTIFY_OIDC_GROUPS_USER to allow any authenticated OIDC user as a normal user.
- 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
- Compare exact group names from a decoded token against config.
- Ensure the groups scope/claim mapping is enabled at the IdP.
- Leave GOTIFY_OIDC_GROUPS_USER empty to allow all authenticated users.
- Re-verify after group renames at the provider.
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
- a local user with the username %s already exists and linking
- the user %s is already bound to a different OIDC identity
- user does not exist and auto-registration is disabled
- groups claim %q is missing
- groups claim %q contains a non-string element: %#v
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/6c9ad77dfe675614.
Report an issue: GitHub.