ASP.NET CORE MVC - Roles and Administrator Authorization
Adding Roles and Administrator Authorization
Create an Admin role, assign an existing Identity user to that role, protect an administrator-only page with [Authorize(Roles = "Admin")], and extend Product authorization so administrators can manage all Products while normal users remain limited to their own records.
By the end of this tutorial, normal users can manage only Products they own, while users in the Admin role can manage the entire Product catalogue and access an administrator-only page.
Part 12 introduced ownership-based authorization. A normal user may Edit or Delete a Product only when Product.OwnerId matches the current Identity user ID. Part 13 adds a second authorization path for administrators.
- Understand role-based authorization
- Enable role services in
Program.cs - Configure the administrator email
- Create an Identity role seeder
- Create the Admin role and assign a user
- Create an administrator-only controller and view
- Add an Admin navigation link
- Extend Product Edit/Delete authorization
- Update Product management links
- Test normal-user and administrator access
- Verify roles in SQLite
- Compare full final files in the appendix
1. Open and Verify the Part 12 Project
cd ~/aspnet-mvc-tutorial/ProductManagement
pwd
dotnet build
code .
The expected path is:
/home/xubuntu/aspnet-mvc-tutorial/ProductManagement
2. Understand Role-Based Authorization
A role represents a named group of permissions or responsibilities. In this tutorial we will use:
Admin
ASP.NET Core can check a role with:
[Authorize(Roles = "Admin")]
3. Enable Role Services in Program.cs
Open:
code Program.cs
Find the existing Identity configuration:
builder.Services
.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
Replace it with:
builder.Services
.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
4. Does Part 13 Need a Migration?
No. The Identity schema created in Part 11 already contains role tables such as AspNetRoles and AspNetUserRoles. Adding role services uses those existing tables.
You do not need dotnet ef migrations add just because AddRoles<IdentityRole>() was added.
5. Choose the Existing Administrator Account
The administrator account must already exist. Use an account registered in Part 11 or later. For this tutorial, the example is:
admin@example.com
Replace that address with your real registered tutorial account.
6. Update appsettings.json
Open:
code appsettings.json
Add an AdminUser section:
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=ProductManagement.db"
},
"AdminUser": {
"Email": "admin@example.com"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
The configuration contains only the email used to locate an already registered user. Do not store the user's password in appsettings.json.
7. Create Data/IdentitySeedData.cs
touch Data/IdentitySeedData.cs
code Data/IdentitySeedData.cs
Add:
using Microsoft.AspNetCore.Identity;
namespace ProductManagement.Data;
public static class IdentitySeedData
{
public static async Task InitializeAsync(
IServiceProvider serviceProvider,
IConfiguration configuration)
{
var roleManager =
serviceProvider.GetRequiredService<
RoleManager<IdentityRole>>();
var userManager =
serviceProvider.GetRequiredService<
UserManager<IdentityUser>>();
const string adminRole = "Admin";
if (!await roleManager.RoleExistsAsync(adminRole))
{
var roleResult = await roleManager.CreateAsync(
new IdentityRole(adminRole));
if (!roleResult.Succeeded)
{
throw new InvalidOperationException(
"Could not create the Admin role.");
}
}
var adminEmail =
configuration["AdminUser:Email"];
if (string.IsNullOrWhiteSpace(adminEmail))
{
return;
}
var adminUser =
await userManager.FindByEmailAsync(adminEmail);
if (adminUser == null)
{
Console.WriteLine(
$"Admin user '{adminEmail}' was not found.");
return;
}
if (!await userManager.IsInRoleAsync(
adminUser,
adminRole))
{
var addRoleResult =
await userManager.AddToRoleAsync(
adminUser,
adminRole);
if (!addRoleResult.Succeeded)
{
throw new InvalidOperationException(
"Could not assign the Admin role.");
}
}
}
}
8. Run the Seeder from Program.cs
Open:
code Program.cs
Find:
var app = builder.Build();
Immediately after it, add:
using (var scope = app.Services.CreateScope())
{
await IdentitySeedData.InitializeAsync(
scope.ServiceProvider,
app.Configuration);
}
9. Build and Run the Seeder
dotnet build
dotnet run
The startup code creates the Admin role if needed and assigns it to the configured user if that user exists.
10. Verify the Role in SQLite
sqlite3 ProductManagement.db
SELECT Id, Name FROM AspNetRoles;
SELECT
AspNetUsers.UserName,
AspNetRoles.Name
FROM AspNetUserRoles
JOIN AspNetUsers
ON AspNetUserRoles.UserId = AspNetUsers.Id
JOIN AspNetRoles
ON AspNetUserRoles.RoleId = AspNetRoles.Id;
.quit
11. Create an Admin-Only Controller
touch Controllers/AdminController.cs
code Controllers/AdminController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ProductManagement.Controllers;
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
public IActionResult Index()
{
return View();
}
}
12. Create the Admin View
mkdir -p Views/Admin
touch Views/Admin/Index.cshtml
code Views/Admin/Index.cshtml
@{
ViewData["Title"] = "Administration";
}
<h1>Administration</h1>
<p>
This page is available only to users
in the Admin role.
</p>
<p>
<a asp-controller="Products"
asp-action="Index"
class="btn btn-primary">
Manage Products
</a>
</p>
13. Add an Admin Link to _Layout.cshtml
Open:
code Views/Shared/_Layout.cshtml
Inside the main navigation list, after the Products link, add:
@if (User.IsInRole("Admin"))
{
<li class="nav-item">
<a class="nav-link text-dark"
asp-area=""
asp-controller="Admin"
asp-action="Index">
Admin
</a>
</li>
}
14. Update Product Authorization for Admin Override
Open:
code Controllers/ProductsController.cs
In Edit GET, Edit POST, Delete GET and Delete POST, find:
if (product.OwnerId != userId)
{
return Forbid();
}
Replace each occurrence with:
if (product.OwnerId != userId
&& !User.IsInRole("Admin"))
{
return Forbid();
}
15. Update Index.cshtml Management Links
Open:
code Views/Products/Index.cshtml
Find the condition that shows Edit/Delete only to the owner. Replace it with:
@if (User.Identity?.IsAuthenticated == true
&& (
product.OwnerId == UserManager.GetUserId(User)
|| User.IsInRole("Admin")
))
{
<text> | </text>
<a asp-action="Edit"
asp-route-id="@product.Id">
Edit
</a>
<text> | </text>
<a asp-action="Delete"
asp-route-id="@product.Id">
Delete
</a>
}
16. Test Normal User Access
Run:
dotnet run
Log in as a normal user and test:
/Admin
The normal user should be denied. The user should still be able to Edit/Delete only Products they own.
17. Test Administrator Access
Log out and sign in using the account configured under AdminUser:Email. If the role was assigned while the user was already logged in, log out and log in again so the authentication cookie is refreshed.
Test:
/Admin
The page should load. The Admin link should also appear in the navbar.
18. Test Administrator Product Management
- Open All Products.
- Find a Product owned by another user.
- Confirm Edit/Delete links are visible.
- Edit the Product.
- Try a legacy Product with
OwnerId = NULL, if one exists. - Confirm Admin can manage it.
19. Role and Ownership Authorization Model
20. Troubleshooting
RoleManager cannot be resolved
Open Program.cs and confirm the Identity configuration contains:
.AddRoles<IdentityRole>()Admin role exists but the user is not assigned
Confirm the email in appsettings.json matches an existing registered user, then restart the application.
User.IsInRole("Admin") remains false
Log out and log in again after role assignment so the authentication cookie and claims are refreshed.
Admin cannot manage another user's Product
Check all four server-side owner checks. They must include the Admin bypass.
21. Hands-On Exercise
- Confirm Admin exists in
AspNetRoles. - Confirm one registered user belongs to Admin.
- Log in as a normal user and verify
/Adminis denied. - Log in as Admin and verify
/Adminopens. - Create a Product as a normal user.
- Log in as Admin and edit that Product.
- Log back in as the normal user and verify another user's Product is still protected.
22. Knowledge Check
- What is a role?
- Why is
AddRoles<IdentityRole>()required? - What does
[Authorize(Roles = "Admin")]do? - Why is no migration required in Part 13?
- What does
RoleManager<IdentityRole>manage? - What does
UserManager.AddToRoleAsync()do? - Why may an administrator need to log out and back in after role assignment?
- How does Product authorization combine ownership and Admin privilege?
23. Part 13 Summary
- enabled role services with
AddRoles<IdentityRole>(); - created and seeded the Admin role;
- assigned an existing user to Admin;
- created an Admin-only controller and view;
- used
[Authorize(Roles = "Admin")]; - added an Admin navbar link;
- extended Product owner checks with an Admin override; and
- kept normal users restricted to their own Products.
Appendix — Full Code for Final Verification
Appendix A — Program.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ProductManagement.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(
builder.Configuration.GetConnectionString(
"DefaultConnection")));
builder.Services
.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
await IdentitySeedData.InitializeAsync(
scope.ServiceProvider,
app.Configuration);
}
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
Appendix B — appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=ProductManagement.db"
},
"AdminUser": {
"Email": "admin@example.com"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Appendix C — Data/IdentitySeedData.cs
using Microsoft.AspNetCore.Identity;
namespace ProductManagement.Data;
public static class IdentitySeedData
{
public static async Task InitializeAsync(
IServiceProvider serviceProvider,
IConfiguration configuration)
{
var roleManager =
serviceProvider.GetRequiredService<
RoleManager<IdentityRole>>();
var userManager =
serviceProvider.GetRequiredService<
UserManager<IdentityUser>>();
const string adminRole = "Admin";
if (!await roleManager.RoleExistsAsync(adminRole))
{
var roleResult = await roleManager.CreateAsync(
new IdentityRole(adminRole));
if (!roleResult.Succeeded)
{
throw new InvalidOperationException(
"Could not create the Admin role.");
}
}
var adminEmail =
configuration["AdminUser:Email"];
if (string.IsNullOrWhiteSpace(adminEmail))
{
return;
}
var adminUser =
await userManager.FindByEmailAsync(adminEmail);
if (adminUser == null)
{
Console.WriteLine(
$"Admin user '{adminEmail}' was not found.");
return;
}
if (!await userManager.IsInRoleAsync(
adminUser,
adminRole))
{
var addRoleResult =
await userManager.AddToRoleAsync(
adminUser,
adminRole);
if (!addRoleResult.Succeeded)
{
throw new InvalidOperationException(
"Could not assign the Admin role.");
}
}
}
}
Appendix D — Controllers/AdminController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ProductManagement.Controllers;
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
public IActionResult Index()
{
return View();
}
}
Appendix E — Views/Admin/Index.cshtml
@{
ViewData["Title"] = "Administration";
}
<h1>Administration</h1>
<p>
This page is available only to users
in the Admin role.
</p>
<p>
<a asp-controller="Products"
asp-action="Index"
class="btn btn-primary">
Manage Products
</a>
</p>
Appendix F — Product Authorization Rule
if (product.OwnerId != userId
&& !User.IsInRole("Admin"))
{
return Forbid();
}
Appendix G — Final Verification Commands
cd ~/aspnet-mvc-tutorial/ProductManagement
dotnet build
dotnet run
Browser checks:
/Products
/Admin
SQLite checks:
sqlite3 ProductManagement.db
SELECT Id, Name
FROM AspNetRoles;
SELECT
AspNetUsers.UserName,
AspNetRoles.Name
FROM AspNetUserRoles
JOIN AspNetUsers
ON AspNetUserRoles.UserId = AspNetUsers.Id
JOIN AspNetRoles
ON AspNetUserRoles.RoleId = AspNetRoles.Id;
.quit
If the Admin role exists, the configured user belongs to it, the Admin page is blocked for normal users, administrators can Edit/Delete any Product, and normal users remain limited to their own Products, Part 13 is complete.
Part 14 will improve the structure of the user interface using _Layout.cshtml, partial views, reusable navigation components, Bootstrap styling, and authentication-aware UI.