How to add Secure Cookie Authentication to your .NET Web App

Step-by-Step how to add Secure Cookie Authentication to your .NET Web App

 

I recently need to implement authorisation and roles to a .NET Web App, it was a bit easier than I thought but still required some time and research. Here's a summary of my findings.

Step 1: Add Authentication Middleware

Update your your program.cs to configure cookie authentication 

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

// Configure Entity Framework and DbContext
builder.Services.AddDbContext<ApplicationDbContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddAuthentication("MyCookieAuth")
    .AddCookie("MyCookieAuth", options =>
    {
        options.LoginPath = "/Account/Login"; // Redirect here for unauthenticated users
        options.LogoutPath = "/Account/Logout"; // Optional logout redirection
        //options.AccessDeniedPath = "/Account/AccessDenied"; // For unauthorized users
        options.Cookie.HttpOnly = true; // Prevent access via JavaScript
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Require HTTPS
        options.Cookie.SameSite = SameSiteMode.Strict; // Prevent cross-site cookie use
    });
var app = builder.Build(); 

app.UseStaticFiles(); 

app.UseRouting(); 

app.UseAuthentication(); 
app.UseAuthorization(); 

app.MapControllers(); // For MVC 
app.MapRazorPages(); // If using Razor Pages 
app.Run();

The key parts above are highlighted in bold. I have included my ApplicationDbContext here so this can be injected into my controllers, this may not required if you are using some other source of User data.

Step 2: Add an AccountController

Under your Controllers folder create an AccountController.cs (name as you require)

  public class AccountController : Controller
  {
      private readonly ApplicationDbContext _dbContext;
      public AccountController(ApplicationDbContext context)
      {
          _dbContext = context;
      }

      [AllowAnonymous]
      public IActionResult Index()
      {
          return View();
      }

      [AllowAnonymous]
      [HttpGet]
      public IActionResult Login() => View();

      [AllowAnonymous]
      [HttpPost]
      public async Task<IActionResult> Login(string username, string password)
      {
          // Hardcoded credentials for simplicity
          // Use the injected AppDbContext to check the database
          var user =  await _dbContext.Contacts
              .FirstOrDefaultAsync(u => u.Firstname == username && u.Password == password);

          if (user != null)
          {
              // Create claims based on user data
              var claims = new List<Claim>
              {
                  new Claim(ClaimTypes.Name, user.Firstname),
                  new Claim(ClaimTypes.Role, (bool)user.IsAdmin ? "IsAdmin" : "User") // Add IsAdmin claim
              };

              // Create identity and principal
              var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
              var principal = new ClaimsPrincipal(identity);

              // Sign in the user
              await HttpContext.SignInAsync("MyCookieAuth", principal);

              // Redirect to the home page
              return RedirectToAction("Index", "Home");
          }

          ViewBag.Error = "Invalid credentials";
          return View();
      }

      public IActionResult Logout()
      {
          HttpContext.SignOutAsync("MyCookieAuth");
          return RedirectToAction("Login");
      }
  }

NOTE: the AccountController picks up the incoming username and password, attempts to locate the user in the databse, if a user record is found we create the claims, an identity and a principal then we call SignInAsync() passing MyCookieAuth as the scheme name, we will use this as the cookie name, change this name to suit your preferences, we then navigae to the home page.

Step 3: Home Controller

for completeness I show below my initial HomeController.cs