Role based Authorization in ASP .NET core

Jun 14, 2024

What is Authorization? 

Authorization verifies whether a user has permission to use specific applications or services. While authentication and authorization are distinct processes, authentication must precede authorization, ensuring the user's identity is confirmed before determining their access rights. 

 

When logging into a system, a user must provide credentials like a username and password to authenticate. Next, the authorization process grants rights. For example, an administrative user can create a document library to add, edit, and delete documents, while a non-administrative user can only read documents in the library. 

Types of Authorization: 

  1. Simple Authorization 

  1. Role-Based Authorization 

  1. Claim-Based Authorization 

  1. Policy-Based Authorization 

I have implemented an example of role-based authorization in .NET.

Step 1: Create one new MVC Web Application with the Authentication type “Individual Account”. 

Step 2: Register Identity with DefaultTokenProvider in the program.cs file.

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

For better understanding, I have added one page to add a new role. 

Create a new method in the controller and add the following code.

[HttpGet]
public IActionResult Admin()
{
    return View();
}

Create a new model Role.cs.

 

namespace Authorization.Models
{
    public class Role
    {
        public string RoleName { get; set; }
    }
}

Create html page for add role. 

@model Role
@{
    ViewData["Title"] = "Admin";
}

<h1>Admin</h1>
<div class="row">
    <div class="col-md-12">
        <form method="post" action="@Url.Action("Admin","Home")">
            <div class="form-group">
                <label>Role Name</label>
                <input type="text" class="form-control" style="width:30%;" asp-for="RoleName" placeholder="Role name" required>
            </div>
            <br />
            <button class="btn btn-success" type="submit">Add</button>
        </form>
    </div>
</div>

I have created a simple page, You can modify the page as per your requirements. 

Add a new method in the controller and add the following code. And declare RoleManager<IdentityRole> and inject in the constructor.

private readonly RoleManager<IdentityRole> _roleManager;

public HomeController(RoleManager<IdentityRole> roleManager)
 {
     
     _roleManager = roleManager;
 }

[HttpPost]
public async Task<IActionResult> Admin(Role role)
{
    var result = _roleManager.RoleExistsAsync(role.RoleName).Result;
    if (!result)
    {
        await _roleManager.CreateAsync(new IdentityRole(role.RoleName));
    }

    return RedirectToAction("Admin");
}

Set a new tab in _Layout.cshtml file to redirect to the Add role page. 

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Admin">Add new role</a>
</li>

Run the project and you will see the output.

Here, You can add a new role. 

Add a new field in register.cshtml using the following code to assign a role to the user. 

<div class="form-floating mb-3">
    <select asp-for="Input.Role" class="form-control" aria-required="true">
        <option value="">Select role</option>
        @foreach (var item in Model.RoleList)
        {
            <option value="@item.Name">@item.Name</option>
        }
    </select>
    <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>

To get the list of roles you can add the following code in your register.cshtml.cs file. And Add  RoleList = _roleManager.Roles in OnPostAsync method also.

public IQueryable<IdentityRole> RoleList { get; set; }

public async Task OnGetAsync(string returnUrl = null)
{
    ReturnUrl = returnUrl;
    RoleList = _roleManager.Roles;
    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}

Run the project and see the output.

Now, Assign the role to the user, and for that add the following code in OnPostAsync after the user is created.

await _userManager.AddToRoleAsync(user, Input.Role);

Full code of register.cshtml.cs file.

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace Authorization.Areas.Identity.Pages.Account
{
    public class RegisterModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly UserManager<IdentityUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly IUserStore<IdentityUser> _userStore;
        private readonly IUserEmailStore<IdentityUser> _emailStore;
        private readonly ILogger<RegisterModel> _logger;
        //private readonly IEmailSender _emailSender;

        public RegisterModel(
            UserManager<IdentityUser> userManager,
            IUserStore<IdentityUser> userStore,
            SignInManager<IdentityUser> signInManager,
            ILogger<RegisterModel> logger, 
            RoleManager<IdentityRole> roleManager
            )
        {
            _userManager = userManager;
            _userStore = userStore;
            _emailStore = GetEmailStore();
            _signInManager = signInManager;
            _roleManager = roleManager;
            _logger = logger;
        }

        [BindProperty]
        public InputModel Input { get; set; }

        public IQueryable<IdentityRole> RoleList { get; set; }

        public string ReturnUrl { get; set; }

        public IList<AuthenticationScheme> ExternalLogins { get; set; }

        public class InputModel
        {
            [Required]
            [EmailAddress]
            [Display(Name = "Email")]
            public string Email { get; set; }

            [Required]
            [Display(Name = "Role")]
            public string Role { get; set; }

            [Required]
            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
            [DataType(DataType.Password)]
            [Display(Name = "Password")]
            public string Password { get; set; }

            [DataType(DataType.Password)]
            [Display(Name = "Confirm password")]
            [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
            public string ConfirmPassword { get; set; }
        }

        public async Task OnGetAsync(string returnUrl = null)
        {
            ReturnUrl = returnUrl;
            RoleList = _roleManager.Roles;
            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
        }

        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl ??= Url.Content("~/");
            RoleList = _roleManager.Roles;
            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            if (ModelState.IsValid)
            {
                var user = CreateUser();

                await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
                await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
                var result = await _userManager.CreateAsync(user, Input.Password);

                if (result.Succeeded)
                {
                    _logger.LogInformation("User created a new account with password.");
                    await _userManager.AddToRoleAsync(user, Input.Role);
                    var userId = await _userManager.GetUserIdAsync(user);
                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                    code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

                    if (_userManager.Options.SignIn.RequireConfirmedAccount)
                    {
                        return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
                    }
                    else
                    {
                        await _signInManager.SignInAsync(user, isPersistent: false);
                        return LocalRedirect(returnUrl);
                    }
                }
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(string.Empty, error.Description);
                }
            }

            return Page();
        }

        private IdentityUser CreateUser()
        {
            try
            {
                return Activator.CreateInstance<IdentityUser>();
            }
            catch
            {
                throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " +
                    $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " +
                    $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml");
            }
        }

        private IUserEmailStore<IdentityUser> GetEmailStore()
        {
            if (!_userManager.SupportsUserEmail)
            {
                throw new NotSupportedException("The default UI requires a user store with email support.");
            }
            return (IUserEmailStore<IdentityUser>)_userStore;
        }
    }
}

Now, register one new user and assign a role to them. For example, I have created one user and assign “Admin” to them.

I have added two new methods in the controller and added a default view for that. 

[Authorize(Roles = "User")]
public IActionResult UserRoleCheck()
{
    return View();
}

[Authorize(Roles = "Admin")]
public IActionResult AdminRoleCheck()
{
    return View();
}

I have set the Authorize attribute with the role name on both authorization methods.

Now, I am running the project and clicking on Admin Role, It will open the page of admin because the logged user role and method role both are the same. 

If I click on User Role, It will give an Access denied error. Because logged user role and method role both are different.

Here, I am using by default access denied page of identity. You can use custom page also, Just set this path to program.cs file.

builder.Services.ConfigureApplicationCookie(options =>
{
    options.AccessDeniedPath = "/Identity/Account/AccessDenied"; // Customize this path as per your application's structure
});

Using this way you will implement the role-based authorization in your application.

Conclusion 

By properly implementing authorization in your applications, you can ensure that resources and sensitive information are accessible only to authorized users. Remember to choose the appropriate authorization technique based on your application’s requirements and complexity.

How to Setup Azure VM
Oct 10, 2024

What is Azure VM? Azure virtual machines (VMs) are a cloud-based computing service that allows users to run applications on the Microsoft Azure platform. VMs are a type of on-demand, scalable computing resource that offers a number of benefits, including:  Security: Azure VMs offer a secure way to run applications.  Affordability: Users can pay for extra VMs when needed and shut them down when not.  Flexibility: Users can choose from various operating systems, including Windows and Linux.  Scalability: Users can scale up to thousands of VMs based on demand or schedules.  Performance: Users can enhance network and storage performance with custom hardware.  Azure virtual machines (VMs) can be created through the Azure portal. This method provides a browser-based user interface to create VMs and their associated resources. In this blog, I'll show you how to use the Azure portal to deploy a virtual machine (VM) in Azure.   Sign in to the Azure portal. Create Virtual Machine Enter Virtual Machine in the search. Under Services, select Virtual machines. In the Virtual Machines page, select Create and then Azure virtual machine. The Create a Virtual Machine page opens. Under Project details, select the resource group. Under the instance details enter the Virtual machine name and choose "Windows Server 2022 Datacenter: Azure Edition - x64 Gen 2" for the Image. Leave the other defaults. Also use can choose image based on your requirement. Under Administrator account, provide a username, such as azureuser, and a password. Under Inbound port rules, choose Allow selected ports and then select RDP (3389) and HTTP (80) from the drop-down. Leave the remaining defaults and then select the Review + create button at the bottom of the page. After validation runs, select the Create button at the bottom of the page. After deployment is complete, select Go to resource. Connect to Virtual Machine On the overview page for your virtual machine, select Connect. Download the RDP file. Open the downloaded RDP file and click Connect when prompted. Click on more choise and enter your username and password that you have added while creating the VM and click on OK. You can see your VM is running now.     Here are some other things to know about Azure VMs:  Maintenance: Users still need to maintain the VM by configuring, patching, and installing software.  Cost: The cost of an Azure VM depends on the size and type of VM, as well as other services used with it.   Security: Users should take steps to ensure the security of their data and applications, such as identity management, encryption, and network protection.  Virtual machine selector: Users can use the virtual machine selector to find the right VMs for their needs and budget.    Conclusion: An Azure virtual machine gives you the flexibility of virtualization without buying and maintaining the physical hardware that runs it. However, you still need to maintain the virtual machine by performing tasks, such as configuring, patching, and installing the software that runs on it.

Serverless computing using Azure Function
Jul 17, 2024

Serverless computing is a widely adopted approach and an extension of the cloud computing model where customers can focus solely on building logic, with the server infrastructure being completely managed by third-party cloud service providers. In Microsoft Azure, serverless computing can be implemented in various ways, one of which is by using Azure Functions. In this blog, we will discuss how to use Azure Functions for serverless computing. Firstly, let us understand the following terms. What Is Serverless Computing? Serverless computing, also known as the Function-as-a-Service (FAAS) approach to building software applications, eliminates the need to manage the server hardware and software by the consumer and be taken care of by third-party vendors. What Are Azure Functions? Azure functions are the serverless solution that provides all the necessary resources to carry out the tasks with minimal lines of code, infrastructure, and cost. The Azure function are a combination of code and event allowing us to write the code in any language. A Step-by-Step Approach For Creating An Azure Function Go to the Azure portal, search for Function App, and select Function App. Create a new Function App and fill in the details accordingly. Basic tab You can select the Runtime stack and version based on your requirements. Here, I am selecting .NET with version 8 and the operating system Windows. Storage You may leave the default values or configure them according to your project requirements. The default values are configured as. Storage account: You may use the existing storage account or create a new account to store your function app. Monitoring Enable the Application insights to monitor the activity. Deployment tab To enable Continuous Integration and Continuous Deployment (CI/CD), you may connect your function app to a repository by authorizing it to GitHub. These are the important things to focus on while creating your function app, you may leave the remaining details as default or customize them according to your requirements. Once you finish configuring your app, you can click the “create” button at the bottom of the page.Now your app will start the process of deployment. Once deployment is done click on go to the resource tab and you will see your function app was created successfully. Now we need to create a function in our function app. As you can see We have various options to choose Visual Studio, VS code, and other editors or CLI. Choose an environment to create your function. I’ve chosen Visual Studio to create my function app. Create an Azure Functions with Visual Studio From the Visual Studio menu, select File > New > Project. In Create a new project, enter functions in the search box, choose the Azure Functions template, and then select Next. Here you can select the function based on your requirements. Here I am selecting Timer trigger function. Then click on the Create button to create a project. You will see that the default Timer trigger function is created. Here I have created one more function called "HTTPTrigger". Here, you can see two JSON files: host.json and local.settings.json. The local.settings.json file stores app settings and settings used by local development tools. Settings in the local.settings.json file are used only when you're running your project locally. When you publish your project to Azure, be sure to also add any required settings to the app settings for the function app. Publish to Azure Use the following steps to publish your project to a function app in Azure. In Solution Explorer, right-click the project and select Publish. In Target, select Azure then Next. Select Azure Function App (Windows) for the Specific target, which creates a function app that runs on Windows, and then select Next. In the Functions instance, You have to select the function that you created on the Azure portal and then click the finish button.  You can see that the publish profile has been added. Now, click on the Publish button to publish the function to Azure. Once the function is published, go to the Azure portal and search for Application Insights. You can find the Application Insights instance with the same name as the function. On the LHS, go to the Transaction search tab under Investigate and click on See all data in the last 24 hours. In the logs, you can see that your function is working properly. Conclusion In a nutshell, Azure functions provide a very precise environment for developers allowing them to more focus on coding rather than then managing infrastructure. This feature plays a key role in building scalable and responsive applications with low cost.

Comparison between Minimal APIs and Controllers
Jun 17, 2024

Introduction  In the ever-evolving landscape of web development, simplicity is key. Enter Minimal APIs in ASP.NET Core, a lightweight and streamlined approach to building web applications. In this detailed blog, we'll explore the concept of Minimal APIs, understand why they matter, and walk through their implementation in ASP.NET Core.    When to Use Minimal APIs?  Minimal APIs are well-suited for small to medium-sized projects, microservices, or scenarios where a lightweight and focused API is sufficient. They shine in cases where rapid development and minimal ceremony are top priorities.  You can find in this blog <link> how to create minimal api.  I am directly showing the comparison between MinimalAPI and controller.    Controllers: Structured and Versatile  Controllers, deeply rooted in the MVC pattern, have been a cornerstone of ASP.NET API development for years. They provide a structured way to organize endpoints, models, and business logic within dedicated controller classes.  Let's consider an example using Microsoft.AspNetCore.Mvc; namespace MinimalAPI.Controllers { [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet(Name = "GetWeatherForecast")] public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } } Advantages of Controllers in Action  Structure and Organization: Controllers offer a clear structure, separating concerns and enhancing maintainability.  Flexibility: They enable custom routes, complex request handling, and support various HTTP verbs.  Testing: Controllers facilitate unit testing of individual actions, promoting a test-driven approach   Minimal APIs: Concise and Swift  With the advent of .NET 6, Minimal APIs emerged as a lightweight alternative, aiming to minimize boilerplate code and simplify API creation.  Here's an example showcasing Minimal APIs.  using MinimalAPI; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); app.MapGet("/GetWeatherForecast", () => { var rng = new Random(); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; var weatherForecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index).Date, TemperatureC = rng.Next(-20, 55), Summary = summaries[rng.Next(summaries.Length)] }).ToArray(); return Results.Ok(weatherForecasts); }); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); Advantages of Minimal APIs in Focus  Simplicity: Minimal APIs drastically reduce code complexity, ideal for smaller projects or rapid prototyping.  Ease of Use: They enable quick API creation with fewer dependencies, accelerating development cycles.  Potential Performance Boost: The reduced overhead might lead to improved performance, especially in smaller applications.    What you choose between MinimalAPI and Controller?  Choosing between Controllers and Minimal APIs hinges on various factors.  Project Scale: Controllers offer better organization and structure for larger projects with intricate architectures.  Development Speed: Minimal APIs shine when speed is crucial, suitable for rapid prototyping or smaller projects.  Team Expertise: Consider your team's familiarity with MVC patterns versus readiness to adopt Minimal APIs.    Conclusion  The decision between Controllers and Minimal APIs for .NET APIs isn't about one being superior to the other. Rather, it's about aligning the choice with the project's specific needs and constraints. Controllers offer robustness and versatility, perfect for larger, complex projects. On the other hand, Minimal APIs prioritize simplicity and rapid development, ideal for smaller, more straightforward endeavours. 

Meet Dobariya

About the Author

Meet Dobariya

Experienced .NET developer proficient in various technologies with a passion for continuous learning. Over 2 years of hands-on experience in software development across multiple domains. Enthusiastic about technology and adept at adapting to new challenges.