This episode covers secure programming practices: input validation with DataAnnotations, preventing OWASP Top 10 attacks in .NET applications, secure storage with the Data Protection API, and preventing SQL injection, XSS, CSRF, and sensitive data leakage.

Security isn't a feature added at the end — it's a way of thinking embedded in every line of code. Most successful attacks exploit vulnerabilities that were actually easy to prevent from the start.
In episode 12 you secured access. Episode 13 goes deeper: how to write code that has no vulnerabilities from the outset. This covers input validation, safe practices against the OWASP Top 10, storing sensitive data, and preventing the most common web attacks.
The first golden rule: never trust user input. ASP.NET Core validates models automatically if you declare the rules with DataAnnotations:
public class DaftarRequest
{
[Required]
[EmailAddress]
public string Email { get; set; } = "";
[Required]
[StringLength(128, MinimumLength = 8)]
public string Password { get; set; } = "";
[Range(0, 150)]
public int Umur { get; set; }
}Because the controller is decorated with [ApiController], requests that fail validation are automatically rejected with status 400 before the method runs. This server-side validation is mandatory — browser-side validation is only for convenience, not security.
The OWASP Top 10 is a periodically updated list of the biggest web security risks. For .NET applications, pay attention to the most relevant ones:
Most of the points above are already handled by the framework if you follow the right patterns, but regular audits are still necessary. Beyond that, many vulnerabilities come not from the framework but from developers assuming input will always behave well — an assumption that should always be questioned.
ASP.NET Core provides the Data Protection API for encrypting data such as cookies and tokens. Data is sealed in a key ring with keys that can be rotated:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/var/lib/toko/keys"))
.SetApplicationName("Toko");
var app = builder.Build();
var protector = app.Services.GetRequiredService<IDataProtectionProvider>()
.CreateProtector("PesanSensitif");
var ciphertext = protector.Protect("data rahasia");
Console.WriteLine(ciphertext);CreateProtector("PesanSensitif") produces encrypted data that can only be opened by an application with the same key ring. The key ring is persisted to the filesystem so it survives restarts — this is what ASP.NET Core uses to protect authentication cookies.
Important: the Data Protection API is not a replacement for encrypting long-lived data such as passwords or credit cards. For data that must persist and be opened by other applications, use standard symmetric encryption techniques like AES. Data Protection is intended for temporary data understood only by the application itself.
Besides encrypting data, make sure your development communication channel is also secure. The local HTTPS certificate can be enabled and trusted with dotnet dev-certs https --trust; this command marks the development certificate as trusted so browsers no longer show warnings. In production, certificates are managed separately by the platform where the app is deployed, but the habit of securing this channel matters in every environment.
SQL injection happens when user input is concatenated raw into a query. In EF Core, always use parameterized:
var aman = await db.Produk
.FromSqlInterpolated($"SELECT * FROM Produk WHERE Nama = {input}")
.ToListAsync();
var buruk = $"SELECT * FROM Produk WHERE Nama = '{input}'";FromSqlInterpolated turns interpolation into safe SQL parameters. Building query strings manually, like the buruk example, opens an injection hole — if the input contains '; DROP TABLE Produk;--, your query can be abused.
Razor Pages and MVC escape HTML output automatically, so XSS is avoided as long as you don't use @Html.Raw without reason. For CSRF, the framework provides anti-forgery tokens that must be included in forms and in requests that change state:
builder.Services.AddAntiforgery(o => o.HeaderName = "X-CSRF-TOKEN");Meanwhile, make sure sensitive data doesn't leak: don't log passwords or tokens, and use environment variables for secrets instead of hardcoding them. You already learned how to store secrets safely in episode 8.
Key takeaways:
In the next episode 14 we unleash the full power of the processor: asynchronous programming and concurrency — the Task-based async pattern, Parallel LINQ and the Task Parallel Library, Channel and async streams, and synchronization primitives and thread safety.