Secure Data in ASP.NET Core Web and API

This post explores essential techniques and best practices to secure data in ASP.NET Core web applications and APIs, as well as various data security standards.

How to Secure Data in ASP.NET Core Web and API

Getting Started

In today's digital landscape, ensuring data security is more important than ever. Whether developing a web application or a RESTful API using ASP.NET Core, implementing robust security measures is essential to prevent sensitive data leakage, uphold user trust, and meet regulatory requirements like GDPR or HIPAA

Here we will cover the data security standards:
  1. Protect Communication
  2. Secure Headers
  3. Authentication and Authorization
  4. Protect Data
  5. Entity Framework Core Security Features
  6. Protect Sensitive Configurations

Here are the essential Data Security Standards and best practices to secure data in ASP.NET Core web applications and APIs.

Protect Communication

Unencrypted communication(HTTP) can be intercepted (Man-in-the-Middle attacks). HTTPS encrypts the data transmitted between clients and the server. Hence protect the comunication between client and server by using HTTPS (SSL/TLS), ollow the below steps to secure data in communication:

  • Force HTTPS by redirecting all HTTP requests:
     public void Configure(IApplicationBuilder app)  
     {  
       app.UseHttpsRedirection();  
       // ...  
     }  
    
  • Require HTTPS globally:
     services.AddMvc(options =>  
     {  
       options.Filters.Add(new RequireHttpsAttribute());  
     });  
    
  • Use HSTS (HTTP Strict Transport Security):
     app.UseHsts(); // Use in production only  
    

Secure Headers

The first responsibility is to secure headers in a request. To secure HTTP headers in ASP.NET Core, you can add middleware or use built-in features to configure and send security-related headers that protect against common web vulnerabilities such as XSS, clickjacking, and MIME sniffing.

Startup.cs or Program.cs
 var builder = WebApplication.CreateBuilder(args);  
 var app = builder.Build();  
 // Secure Headers Middleware  
 app.Use(async (context, next) =>  
 {  
   context.Response.Headers.Add("X-Content-Type-Options", "nosniff");  
   context.Response.Headers.Add("X-Frame-Options", "DENY");  
   context.Response.Headers.Add("Referrer-Policy", "no-referrer");  
   context.Response.Headers.Add("Permissions-Policy", "geolocation=(), microphone=()");  
   context.Response.Headers.Add("Cross-Origin-Opener-Policy", "same-origin");  
   context.Response.Headers.Add("Cross-Origin-Embedder-Policy", "require-corp");  
   // Content Security Policy (adjust as needed)  
   context.Response.Headers.Add("Content-Security-Policy",  
     "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none';");  
   await next();  
 });  
 // HSTS (use only in production over HTTPS)  
 app.UseHsts();  
 app.UseHttpsRedirection();  
 app.UseStaticFiles();  
 app.UseRouting();  
 app.UseAuthorization();  
 app.MapControllers();  
 app.Run();  

Authentication and Authorization

  1. Authentication
    • Authenticate users using built-in providers (Identity, OAuth, JWT, etc.).
    • Enable ASP.NET Core Identity: add dotnet package Microsoft.AspNetCore.Identity.EntityFrameworkCore and configure Identity in Startup.cs or program.cs:
       services.AddIdentity<ApplicationUser, IdentityRole>()  
         .AddEntityFrameworkStores<ApplicationDbContext>();  
      
    • Enable JWT for APIs:
       services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)  
         .AddJwtBearer(options =>  
         {  
           options.TokenValidationParameters = new TokenValidationParameters  
           {  
             ValidateIssuer = true,  
             ValidateAudience = true,  
             ValidateLifetime = true,  
             ValidateIssuerSigningKey = true,  
             // ...  
           };  
         });  
      
  2. Authorization
    • Use role-based or policy-based authorization:
       [Authorize(Roles = "Admin")]  
       public IActionResult AdminOnly() => View();  
      
    • For APIs:
       [Authorize(Policy = "CanAccessData")]  
       [HttpGet("secure-data")]  
       public IActionResult GetSecureData() => Ok(Data);  
      

Protect Data using (DPAPI)

The Data Protection API(DPAPI) in ASP.NET Core is designed to provide cryptographic services for protecting data, such as encrypting cookies, tokens, or any sensitive information. It abstracts encryption logic and allows you to persist keys across deployments, use different key storage mechanisms, and manage key rotation.

Install Required Package

If you're not using a full ASP.NET Core app (e.g., minimal API or console), make sure to include the necessary package:

 dotnet add package Microsoft.AspNetCore.DataProtection  

Register Data Protection Services
In Setup.cs or Program.cs
 using Microsoft.AspNetCore.DataProtection;  
 var builder = WebApplication.CreateBuilder(args);  
 // Add Data Protection service  
 builder.Services.AddDataProtection()  
   .PersistKeysToFileSystem(new DirectoryInfo(@"./keys")) // Optional: Persist keys  
   .SetApplicationName("MyAppName");           // Use the same name across apps for shared protection  
 var app = builder.Build();  

Inject and Use IDataProtector

You can use IDataProtectionProvider to create a IDataProtector instance and use it to protect/unprotect strings.

In Controller
 using Microsoft.AspNetCore.DataProtection;  
 using Microsoft.AspNetCore.Mvc;  
 [ApiController]  
 [Route("api/[controller]")]  
 public class SecureController : ControllerBase  
 {  
   private readonly IDataProtector _protector;  
   public SecureController(IDataProtectionProvider provider)  
   {  
     _protector = provider.CreateProtector("SecureController.PurposeString");  
   }  
   [HttpGet("protect")]  
   public IActionResult Protect([FromQuery] string input)  
   {  
     var protectedData = _protector.Protect(input);  
     return Ok(protectedData);  
   }  
   [HttpGet("unprotect")]  
   public IActionResult Unprotect([FromQuery] string protectedInput)  
   {  
     try  
     {  
       var unprotectedData = _protector.Unprotect(protectedInput);  
       return Ok(unprotectedData);  
     }  
     catch  
     {  
       return BadRequest("Invalid or expired protected string.");  
     }  
   }  
 }  

Protect Sensitive Configurations

Protecting sensitive configuration data (like connection strings, API keys, secrets, etc.) in an ASP.NET Core Web and API application is critical for maintaining application security.

Use appsettings.json Safely

Avoid storing sensitive data directly in appsettings.json, especially in production. If you must, never commit it to source control.

Instead, use placeholder keys:
 {  
  "ConnectionStrings": {  
   "DefaultConnection": "YourDatabaseConnectionStringHere"  
  },  
  "ApiKeys": {  
   "ExternalService": "UseSecretsManagerInstead"  
  }  
 }  

Do NOT Check Secrets into Source Control

Use .gitignore to exclude files like secrets.json or development-specific settings.

 # .gitignore  
 secrets.json  
 appsettings.Development.json  

Use Environment Variables

Set environment variables for your configuration data on your hosting environment or in Docker/Kubernetes.

Example:
 export ConnectionStrings__DefaultConnection="your-prod-connection-string"  
Note:- ASP.NET Core reads __ as : when mapping keys.

Use Azure Key Vault or AWS/GCP Secret Managers

For enterprise/production apps, use a cloud secret manager:

  • Azure Key Vault: ASP.NET Core built-in support
  • AWS Secrets Manager: Use the AWS SDK or integrations.
  • Google Secret Manager Use Google libraries and identity setup.
Protect appsettings with Encryption (Advanced)

Encrypt/decrypt parts of your configuration manually. Encrypt config sections before deployment, and decrypt them at runtime using middleware or a custom provider. But don’t roll your own crypto unless you really need to. Prefer managed services like Azure Key Vault.

Validate Configuration at Startup
Fail fast if required configuration is missing:
 var mySecret = builder.Configuration["MySecrets:ApiKey"];  
 if (string.IsNullOrEmpty(mySecret))  
 {  
   throw new Exception("Missing API Key!");  
 }  

Role-based Configuration Access
Avoid injecting entire IConfiguration into services. Instead, bind only what's needed:
 builder.Services.Configure<MyApiSettings>(  
   builder.Configuration.GetSection("MyApiSettings"));  
Then inject IOptions<MyApiSettings> into services.

Use Entity Framework Core Security Features

Entity Framework Core (EF Core) provides several security features that you can integrate into an ASP.NET Core web app or API. However, EF Core itself is primarily an Object-Relational Mapper (ORM), so its "security features" mainly focus on data access, such as:

Prevent SQL Injection

EF Core automatically uses parameterized queries, which protects against SQL Injection.

Example:
 var user = await context.Users  
   .Where(u => u.Username == username)  
   .FirstOrDefaultAsync();  

Never use raw SQL with string concatenation
Example:
 context.Users.FromSqlRaw($"SELECT * FROM Users WHERE Username = '{username}'");  

You can use Insted
 context.Users.FromSqlRaw("SELECT * FROM Users WHERE Username = @p0", username);  

Restrict Data Access Based on User
Example:
 [Authorize]  
 [HttpGet]  
 public async Task<IActionResult> GetUserData()  
 {  
   var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;  
   var data = await _context.Orders  
     .Where(o => o.UserId == userId)  
     .ToListAsync();  
   return Ok(data);  
 }  

Validate and Sanitize Input
Example:
 public class RegisterDto  
 {  
   [Required]  
   [EmailAddress]  
   public string Email { get; set; }  
   [Required]  
   [MinLength(6)]  
   public string Password { get; set; }  
 }  

Avoid Overposting
Example:
 public class CreateUserDto  
 {  
   public string Username { get; set; }  
   public string Email { get; set; }  
 }  
 [HttpPost]  
 public async Task<IActionResult> Create(CreateUserDto dto)  
 {  
   var user = new User  
   {  
     Username = dto.Username,  
     Email = dto.Email  
   };  
   _context.Users.Add(user);  
   await _context.SaveChangesAsync();  
   return Ok();  
 }  

Dont do:
 [HttpPost]  
 public async Task<IActionResult> Create(User user) // ← Exposes everything  
 {  
   _context.Users.Add(user);  
   await _context.SaveChangesAsync();  
   return Ok();  
 }  

Use ASP.NET Core Identity with EF Core
Example: Startup.cs (or Program.cs)
 builder.Services.AddDbContext<ApplicationDbContext>(options =>  
   options.UseSqlServer(connectionString));  
 builder.Services.AddDefaultIdentity<IdentityUser>(options =>   
   options.SignIn.RequireConfirmedAccount = true)  
   .AddEntityFrameworkStores<ApplicationDbContext>();  

Cross-Site Scripting (XSS) Protection

To protect your ASP.NET Core Web Applications and APIs against Cross-Site Scripting (XSS) attacks, you need a multi-layered approach. Below are best practices and built-in features to mitigate XSS in both Razor Pages/MVC and Web APIs.

  1. Automatic HTML Encoding (Razor Pages / MVC Views): ASP.NET Core Razor automatically encodes output to prevent XSS.
  2. Tag Helpers & Form Handling: Tag Helpers encode content by default. For example:
     <input asp-for="UserName" />  
    
  3. Input Validation & Sanitization: Use Model Validation to validate user inputs. For rich text (HTML) inputs, sanitize it using libraries like:
     var sanitizer = new HtmlSanitizer();  
     string safeHtml = sanitizer.Sanitize(userInput);  
    

Summary

Securing an ASP.NET Core application is not a one-time task,it is an ongoing process. Always follow the principle of least privilege, encrypt sensitive data, and stay up to date with best practices and tools to reduce vulnerabilities.

Thanks

Kailash Chandra Behera

I am an IT professional with over 13 years of experience in the full software development life cycle for Windows, services, and web-based applications using Microsoft .NET technologies.

Previous Post Next Post

نموذج الاتصال