Skip to content

Security in C# — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Security in C#. We cover key concepts, practical examples, and best practices to help you master this topic.

Hook

Security is not optional -- it is a fundamental requirement for every application. C# and .NET provide comprehensive libraries for authentication, authorization, encryption, and data protection. Understanding these security primitives helps you build applications that protect user data and resist attacks.

Learning Path

graph LR
  A[Security] --> B[Authentication]
  A --> C[Authorization]
  B --> D[JWT Tokens]
  B --> E[Identity Framework]
  C --> F[Policies]
  style A fill:#4a90d9,color:#fff
  style B fill:#4a90d9,color:#fff
  style C fill:#4a90d9,color:#fff
  style D fill:#4a90d9,color:#fff
  style E fill:#4a90d9,color:#fff
  style F fill:#4a90d9,color:#fff

Authentication Vs Authorization

Authentication verifies identity; authorization controls access.

// Authentication: Who are you?
// Authorization: What can you do?

[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
    [HttpGet("public")]
    public IActionResult Public() => Ok("Anyone can access this");

    [HttpGet("authenticated")]
    [Authorize]
    public IActionResult Authenticated() =>
        Ok($"Hello {User.Identity?.Name}");

    [HttpGet("admin-only")]
    [Authorize(Roles = "Admin")]
    public IActionResult AdminOnly() =>
        Ok("Only admins can see this");

    [HttpGet("policy")]
    [Authorize(Policy = "Over21")]
    public IActionResult PolicyBased() =>
        Ok("Age verified access");
}

JWT Authentication

JSON Web Tokens are the standard for stateless authentication.

// Program.cs
var builder = WebApplication.CreateBuilder(args);

var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(jwtSettings["Key"]!));

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = jwtSettings["Issuer"],
            ValidAudience = jwtSettings["Audience"],
            IssuerSigningKey = key,
            ClockSkew = TimeSpan.Zero
        };
    });

builder.Services.AddAuthorization();
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

// Token generation service
public class TokenService
{
    private readonly IConfiguration _config;

    public TokenService(IConfiguration config) => _config = config;

    public string GenerateToken(string username, string[] roles)
    {
        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new List<Claim>
        {
            new(ClaimTypes.Name, username),
            new(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
            new("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
        };
        claims.AddRange(roles.Select(r => new Claim(ClaimTypes.Role, r)));

        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: credentials
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

ASP.NET Core Identity

Identity provides full user management with EF Core.

// Install: dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore

public class AppUser : IdentityUser
{
    public string? DisplayName { get; set; }
    public DateTime RegisteredAt { get; set; }
}

public class AppDbContext : IdentityDbContext<AppUser>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString));

builder.Services.AddIdentity<AppUser, IdentityRole>(options =>
{
    // Password settings
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 8;
    options.Password.RequireNonAlphanumeric = true;

    // Lockout settings
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);

    // User settings
    options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();

Authorization Policies

Create reusable authorization policies.

// In Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Over21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));

    options.AddPolicy("EmployeeOnly", policy =>
        policy.RequireClaim("EmployeeId"));

    options.AddPolicy("AtLeastManager", policy =>
        policy.RequireRole("Admin", "Manager"));
});

// Custom requirement
public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }
    public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;
}

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        var ageClaim = context.User.FindFirst(c =>
            c.Type == "Age" && int.TryParse(c.Value, out int age));

        if (ageClaim != null && int.Parse(ageClaim.Value) >= requirement.MinimumAge)
            context.Succeed(requirement);

        return Task.CompletedTask;
    }
}

// Register handler
builder.Services.AddScoped<IAuthorizationHandler, MinimumAgeHandler>();

Data Protection

Protect sensitive data with the data protection API.

// Program.cs
builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("/keys"))
    .ProtectKeysWithDpapi()
    .SetApplicationName("MyApp");

// In a service
public class DataProtectionService
{
    private readonly IDataProtector _protector;

    public DataProtectionService(IDataProtectionProvider provider)
    {
        _protector = provider.CreateProtector("Payment.Encryption.v1");
    }

    public string Encrypt(string plaintext) => _protector.Protect(plaintext);
    public string Decrypt(string ciphertext) => _protector.Unprotect(ciphertext);
}

Common Security Vulnerabilities

// SQL Injection (BAD)
var cmd = $"SELECT * FROM Users WHERE Name = '{input}'";

// SQL Injection (GOOD)
var cmd = "SELECT * FROM Users WHERE Name = @name";
cmd.Parameters.AddWithValue("@name", input);

// XSS (BAD)
ViewBag.Message = userInput;

// XSS (GOOD)
ViewBag.Message = HtmlEncoder.Default.Encode(userInput);

// CSRF
// ASP.NET Core includes anti-forgery tokens by default
// Use [AutoValidateAntiforgeryToken] attribute

// Open Redirect
// Validate redirect URLs against an allowlist
if (!allowedUrls.Contains(returnUrl))
    returnUrl = "/";

Common Mistakes

  1. Storing passwords in plaintext: Always hash passwords with BCrypt, Argon2, or ASP.NET Core Identity.

  2. Not validating JWT tokens: Always validate issuer, audience, lifetime, and signing key on every request.

  3. Over-sharing error details: Never expose stack traces or internal error details to clients. Return generic error messages.

  4. Missing HTTPS: Always enforce HTTPS in production. Use HSTS headers to prevent protocol downgrade attacks.

  5. Ignoring Rate Limiting: Protect APIs from brute force attacks with rate limiting middleware (.NET 7+).

Practice Questions

  1. Implement a registration and login endpoint with ASP.NET Core Identity.

  2. Create a custom authorization policy that checks if the user belongs to a specific department claim.

  3. Write a middleware that validates API keys for a machine-to-machine endpoint.

  4. Challenge: Build a multi-factor authentication flow using TOTP (Time-based One-Time Password).

FAQ

Should I use JWT or session-based authentication?

JWT is stateless and suitable for APIs and distributed systems. Session-based auth is simpler for traditional web apps with server-side state.

How do I securely store API keys?

Use Azure Key Vault, HashiCorp Vault, or environment variables. Never hardcode secrets or commit them to source control.

What is the best hashing algorithm for passwords?

Use Argon2 (via Konscious.Security.Cryptography.Argon2) or BCrypt. Never use MD5 or SHA-1 for passwords.

How do I prevent brute force attacks?

Implement rate limiting, account lockout after failed attempts, and CAPTCHA for login endpoints.

Is it safe to send JWT tokens over HTTP?

No. Always use HTTPS to prevent token interception. Set the Secure flag on cookies.

Mini Project: JWT Authentication API

Build a complete authentication API with JWT tokens.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly IConfiguration _config;

    public AuthController(IConfiguration config) => _config = config;

    [HttpPost("login")]
    public IActionResult Login([FromBody] LoginRequest request)
    {
        // In production: verify against Identity or user store
        if (request.Username != "admin" || request.Password != "password")
            return Unauthorized(new { error = "Invalid credentials" });

        var token = GenerateJwt(request.Username, new[] { "User", "Admin" });

        return Ok(new
        {
            token,
            expiresAt = DateTime.UtcNow.AddHours(1)
        });
    }

    [HttpGet("profile")]
    [Authorize]
    public IActionResult Profile()
    {
        return Ok(new
        {
            Username = User.Identity?.Name,
            Roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value),
            Claims = User.Claims.Select(c => new { c.Type, c.Value })
        });
    }

    private string GenerateJwt(string username, string[] roles)
    {
        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new List<Claim>
        {
            new(ClaimTypes.Name, username),
            new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };
        claims.AddRange(roles.Select(r => new Claim(ClaimTypes.Role, r)));

        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: creds);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

public record LoginRequest(string Username, string Password);

API Test:

POST /api/auth/login {"username":"admin","password":"password"}
Response: {"token":"eyJhbGci...", "expiresAt":"2026-06-28T..."}

GET /api/auth/profile
Authorization: Bearer eyJhbGci...
Response: {"username":"admin","roles":["User","Admin"]}

Security is a critical skill for every C# developer. The .NET platform provides robust, well-tested libraries for authentication, authorization, and data protection that help you build secure applications by default.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro