fullstackhero/dotnet-starter-kit · error · ForbiddenException
The root tenant administrator cannot be demoted.
Error message
The root tenant administrator cannot be demoted.
What it means
Thrown as a ForbiddenException when the target of a role change is the root tenant's seed administrator. That account is the framework's last-resort recovery login, so demoting it is unconditionally disallowed regardless of who is asking.
Solutions
- Exclude the root tenant's seed admin from any role-removal payloads.
- If you truly need to change that account, do it via a database/seed change, not the role API.
- Filter it out in the admin UI's editable-user list.
- Ensure bulk operations fetch the root admin id and skip it.
Example fix
// before: bulk payload includes root admin await userRoleService.AssignRolesAsync(rootAdminId, rolesWithoutAdmin, ct); // after if (userRoleService.IsRootTenantAdminUser(rootAdminId)) return; // never demote seed admin
Defensive patterns
Strategy: validation
Validate before calling
if (isRootTenantSeedAdmin(userId)) throw new Error("The root tenant seed admin cannot be modified via the roles API."); Try / catch
try { await assignRoles(userId, roles); } catch (e) {
if (e.status === 403 && /root tenant administrator/i.test(e.message)) notify("Seed admin is protected");
else throw e;
} Prevention
- Hard-code an exclusion for the root seed admin id in bulk operations.
- Hide the seed admin from editable user lists.
- Treat changes to that account as database/seed-level operations only.
When it happens
Trigger: AssignRolesAsync called against the root-tenant seed admin's userId with any payload that removes its Admin role; Verify IsRootTenantAdmin matched (root tenant + seed admin identity).
Common situations: Bulk user-management operations sweeping the seed admin into a role change; cleanup scripts deleting/degrading 'unused' admin accounts in the root tenant.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Administrators cannot remove their own admin role.
- Cannot revoke session for another user
- Cannot revoke sessions for another user
- Cannot view sessions for another user
- Channel admin role required.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/7263306161cef90a.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/UserRoleService.cs:99
{
return;
}
// Administrators cannot demote themselves — they would lose access immediately on the next request,
// and would need another admin to restore them.
var actorId = currentUser.GetUserId();
if (actorId != Guid.Empty && string.Equals(actorId.ToString(), user.Id, StringComparison.Ordinal))
{
throw new CustomException(
"Administrators cannot remove their own admin role.",
Array.Empty<string>(),
HttpStatusCode.BadRequest);
}
// The root tenant's seed admin is the framework's last-resort recovery account.
if (IsRootTenantAdmin(user))
{
throw new ForbiddenException("The root tenant administrator cannot be demoted.");
}
// After this removal, at least one admin must remain in the tenant — matches
// the "at least one active administrator" invariant enforced on user deactivation.
await EnsureMinimumAdminCountAsync();
}
private bool IsRootTenantAdmin(FshUser user)
{
return user.Email == MultitenancyConstants.Root.EmailAddress
&& multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id == MultitenancyConstants.Root.Id;
}
private async Task EnsureMinimumAdminCountAsync()
{
int adminCount = (await userManager.GetUsersInRoleAsync(RoleConstants.Admin)).Count;
if (adminCount <= 1)
{View on GitHub (pinned to 3f2959e683)