fullstackhero/dotnet-starter-kit · error · CustomException
Administrators cannot remove their own admin role.
Error message
Administrators cannot remove their own admin role.
What it means
Thrown by ValidateAdminRoleChangeAsync (invoked from AssignRolesAsync) when an administrator attempts to remove the Admin role from their own account. Guard prevents self-lockout — the actor would lose admin access on the very next request with no one to restore it.
Solutions
- Have a different administrator perform the demotion of this account.
- Exclude the current user's id from bulk role-update payloads client-side.
- If self-change is genuinely required, remove Admin and re-add it in one call is still blocked — plan a second admin first.
- In tooling, skip the actor's own account when iterating role updates.
Example fix
// before: submits every row, including the acting admin foreach (var row in allUsers) await assignRolesAsync(row.Id, row.Roles, ct); // after: skip self foreach (var row in allUsers.Where(u => u.Id != currentUserId)) await assignRolesAsync(row.Id, row.Roles, ct);
Defensive patterns
Strategy: validation
Validate before calling
const payload = roles.filter(r => !(userId === currentUserId && r.role === "Admin" && !r.assigned));
if (payload.length !== roles.length) warn("You cannot remove your own Admin role."); Try / catch
try { await assignRoles(userId, roles); } catch (e) {
if (e.status === 400 && /own admin role/i.test(e.message)) notify("Ask another admin to make this change");
else throw e;
} Prevention
- Disable self-demotion controls in the admin UI.
- Exclude the actor's id from bulk role-update loops.
- Keep at least two admins so changes can be made by the other one.
When it happens
Trigger: An authenticated Admin sends a role-assignment payload for their own userId whose entries remove or unassign the Admin role.
Common situations: Admin testing role changes on themselves; bulk role-edit UI submitting the current user's row with Admin unchecked; scripts looping over users including the acting admin.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The root tenant administrator cannot be demoted.
- 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/5474871a95ce0439.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/UserRoleService.cs:90
{
bool isRemovingAdminRole = userRoles.Exists(a => !a.Enabled && a.RoleName == RoleConstants.Admin);
if (!isRemovingAdminRole)
{
return;
}
bool userIsAdmin = await userManager.IsInRoleAsync(user, RoleConstants.Admin);
if (!userIsAdmin)
{
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)
{View on GitHub (pinned to 3f2959e683)