In this guide, we’ll walk through the implementation of a custom role-based authorization system in ASP.NET Core. This system includes user registration, login, role management, and secure endpoint access using custom authorization attributes. We’ll also cover how to set up the project using EF Core Database-First, install the necessary NuGet packages, and configure Swagger for testing.
Step-by-Step: Package Installation and EF Core Database-First Setup
1. Install Required NuGet Packages
Open your project in Visual Studio or your preferred IDE, and install the following NuGet packages:
Core Packages
- Microsoft.EntityFrameworkCore: The main EF Core package.
- Microsoft.EntityFrameworkCore.SqlServer: For SQL Server database support.
- Microsoft.EntityFrameworkCore.Tools: For EF Core migrations and scaffolding.
-
Microsoft.EntityFrameworkCore.Design: Required for EF Core design-time tools (e.g.,
Scaffold-DbContext
).
Run the following commands in the Package Manager Console:
Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.Design
Additional Packages
- Effortless.Net.Encryption: For password hashing and token encryption.
- Newtonsoft.Json: For JSON serialization and deserialization.
- Swashbuckle.AspNetCore: For Swagger API documentation.
Run the following commands:
Install-Package Effortless.Net.Encryption
Install-Package Newtonsoft.Json
Install-Package Swashbuckle.AspNetCore
2. Set Up the Database
Create the database using the following SQL scripts:
Tbl_User
Stores user information, including username, hashed password, and email.
CREATE TABLE Tbl_User (
UserId INT PRIMARY KEY IDENTITY(1,1),
Username NVARCHAR(100) NOT NULL,
Password NVARCHAR(256) NOT NULL, -- Hashed password
Email NVARCHAR(100) NOT NULL
);
Tbl_Role
Stores roles and their descriptions.
CREATE TABLE Tbl_Role (
RoleId INT PRIMARY KEY IDENTITY(1,1),
RoleName NVARCHAR(50) NOT NULL,
RoleDescription NVARCHAR(255)
);
Tbl_RolePermission
Links users to their roles.
CREATE TABLE Tbl_RolePermission (
RolePermissionId INT PRIMARY KEY IDENTITY(1,1),
RoleId INT NOT NULL,
UserId INT NOT NULL,
FOREIGN KEY (RoleId) REFERENCES Tbl_Role(RoleId),
FOREIGN KEY (UserId) REFERENCES Tbl_User(UserId)
);
Tbl_UserLogin
Tracks user login sessions.
CREATE TABLE Tbl_UserLogin (
UserLoginId INT PRIMARY KEY IDENTITY(1,1),
UserId INT NOT NULL,
SessionId NVARCHAR(50) NOT NULL,
SessionExpiredDate DATETIME NOT NULL,
LogoutDate DATETIME NULL,
FOREIGN KEY (UserId) REFERENCES Tbl_User(UserId)
);
3. Scaffold the Database Using EF Core
Use the Scaffold-DbContext
command to generate the entity classes and DbContext
from your database. Replace the placeholders with your database connection details:
Scaffold-DbContext "Server=YOUR_SERVER_NAME;Database=YOUR_DATABASE_NAME;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Database/Models -ContextDir Database/Data -Context ApplicationDbContext
Verify the Generated Files
After running the command, you should see the following files in your project:
-
Entity Classes: Generated in the
Database/Models
folder (e.g.,TblUser.cs
,TblRole.cs
). -
DbContext Class: Generated in the
Database/Data
folder (e.g.,ApplicationDbContext.cs
).
4. Configure the DbContext in Program.cs
Register the ApplicationDbContext
in the dependency injection container. Open Program.cs
and add the following code:
var builder = WebApplication.CreateBuilder(args);
// Add DbContext with SQL Server
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
Add Connection String to appsettings.json
Add your database connection string to the appsettings.json
file:
{
"ConnectionStrings": {
"DefaultConnection": "Server=YOUR_SERVER_NAME;Database=YOUR_DATABASE_NAME;Trusted_Connection=True;"
}
}
Custom Role-Based Authorization System
1. Authentication and User Services
AuthService
Handles user login and token generation.
public class AuthService
{
private readonly ApplicationDbContext _db;
public AuthService(ApplicationDbContext db)
{
_db = db;
}
public string Login(string username, string password)
{
var user = _db.TblUsers.FirstOrDefault(u => u.Username == username);
if (user == null || !Hash.Verify(HashType.SHA256, password, "YourSalt", false, user.Password))
{
throw new UnauthorizedAccessException("Invalid credentials");
}
string sessionId = Ulid.NewUlid().ToString();
var roles = _db.TblRolePermissions
.Where(rp => rp.UserId == user.UserId)
.Select(rp => rp.Role.RoleName)
.ToList();
_db.TblUserLogins.Add(new TblUserLogin
{
UserId = user.UserId,
SessionId = sessionId,
SessionExpiredDate = DateTime.UtcNow.AddHours(1), // 1-hour session
LogoutDate = null
});
_db.SaveChanges();
return TokenService.GenerateToken(user.UserId, sessionId, roles);
}
}
UserService
Handles user registration and role assignment.
public class UserService
{
private readonly ApplicationDbContext _db;
public UserService(ApplicationDbContext db)
{
_db = db;
}
public void RegisterUser(string username, string password, string email, List<string> roles)
{
string hashedPassword = Hash.Create(HashType.SHA256, password, "YourSalt", false);
var user = new TblUser
{
Username = username,
Password = hashedPassword,
Email = email
};
_db.TblUsers.Add(user);
_db.SaveChanges();
foreach (var roleName in roles)
{
var role = _db.TblRoles.FirstOrDefault(r => r.RoleName == roleName);
if (role != null)
{
_db.TblRolePermissions.Add(new TblRolePermission
{
RoleId = role.RoleId,
UserId = user.UserId
});
}
}
_db.SaveChanges();
}
}
2. Token Service
The TokenService
class handles token generation and decryption using symmetric encryption.
public static class TokenService
{
private static readonly string EncryptionKey = "YourSecretEncryptionKey";
public static string GenerateToken(int userId, string sessionId, List<string> roles)
{
var tokenData = new
{
UserId = userId,
SessionId = sessionId,
Roles = roles
};
string jsonToken = JsonConvert.SerializeObject(tokenData);
byte[] encryptedBytes = AES.Encrypt(jsonToken, EncryptionKey, Bytes.GenerateSalt());
return Convert.ToBase64String(encryptedBytes);
}
public static (int UserId, string SessionId, List<string> Roles) DecryptToken(string encryptedToken)
{
byte[] encryptedBytes = Convert.FromBase64String(encryptedToken);
string jsonToken = AES.Decrypt(encryptedBytes, EncryptionKey, Bytes.GenerateSalt());
return JsonConvert.DeserializeObject<(int UserId, string SessionId, List<string> Roles)>(jsonToken);
}
}
3. Custom Authorization Attribute
The CustomAuthorizeAttribute
enforces role-based access control on endpoints.
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
private readonly string[] _requiredRoles;
public CustomAuthorizeAttribute(params string[] requiredRoles)
{
_requiredRoles = requiredRoles;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var token))
{
context.Result = new UnauthorizedResult(); // 401 Unauthorized
return;
}
try
{
var (userId, sessionId, roles) = TokenService.DecryptToken(token);
using (var db = new ApplicationDbContext())
{
var session = db.TblUserLogins
.FirstOrDefault(ul => ul.UserId == userId && ul.SessionId == sessionId && ul.SessionExpiredDate > DateTime.UtcNow && ul.LogoutDate == null);
if (session == null)
{
context.Result = new UnauthorizedResult(); // 401 Unauthorized
return;
}
}
if (_requiredRoles.Length > 0 && !_requiredRoles.Any(role => roles.Contains(role)))
{
// Return 403 Forbidden if the user doesn't have the required roles
context.Result = new StatusCodeResult(403); // 403 Forbidden
return;
}
// Store user information in HttpContext for later use
context.HttpContext.Items["UserId"] = userId;
context.HttpContext.Items["Roles"] = roles;
}
catch
{
context.Result = new UnauthorizedResult(); // 401 Unauthorized
}
}
}
4. Secure Endpoints
The SecureController
demonstrates how to use the CustomAuthorizeAttribute
to protect endpoints.
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
[HttpGet("admin")]
[CustomAuthorize("Admin")] // Requires "Admin" role
public IActionResult AdminEndpoint()
{
return Ok("Welcome, Admin!");
}
[HttpGet("user")]
[CustomAuthorize("User")] // Requires "User" role
public IActionResult UserEndpoint()
{
return Ok("Welcome, User!");
}
}
5. Swagger Configuration
Configure Swagger to include support for the Authorization
header.
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "CustomRoleBasedAuthorization", Version = "v1" });
// Add support for the Authorization header in Swagger
options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Description = "Custom Authorization header using an API key. Example: \"Authorization: <your-key>\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "ApiKey"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "ApiKey"
}
},
Array.Empty<string>()
}
});
});
6. Register Services in Program.cs
Register the AuthService
and UserService
in the dependency injection container.
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<UserService>();
Now You Can Run, Test, and Enjoy Coding!
With everything set up, you’re ready to run the application, test the endpoints, and see your custom role-based authorization system in action. Use tools like Swagger or Postman to interact with your API and verify that the authentication and authorization logic works as expected.
Explore the Source Code
If you'd like to dive deeper into the implementation, feel free to explore the complete source code on GitHub. You can find the repository here:
🔗 CustomRoleBasedAuthorization on GitHub
Feel free to clone the repository, experiment with the code, and adapt it to your own projects. If you find it helpful, don’t forget to give it a ⭐️ on GitHub!
Happy coding! 🚀
Top comments (0)