C# security best practices help .NET teams prevent vulnerabilities before code reaches production. In practice, secure C# means validating inputs, using safe framework APIs, enforcing authorization close to data access, avoiding unsafe deserialization, protecting secrets, and scanning both code and NuGet dependencies.
This guide targets C# web applications, ASP.NET Core APIs, background services, and internal tools. It also covers related search intents such as C# secure coding, secure coding practices C#, .NET security best practices, and ASP.NET security best practices.
C# security checklist
Use this checklist during design review and pull request review:
- Validate every request model on the server.
- Use EF Core LINQ or parameterized SQL; never concatenate user input into queries.
- Enforce authorization on every resource and object access.
- Use ASP.NET Core authentication and authorization middleware correctly.
- Protect cookies with
HttpOnly,Secure, andSameSite. - Enable CSRF protection for browser form flows.
- Avoid unsafe polymorphic deserialization.
- Use platform cryptography primitives instead of custom crypto.
- Store secrets in a secret manager, not source control.
- Keep NuGet packages current and remove unused dependencies.
- Avoid leaking stack traces, connection strings, tokens, or PII in logs.
- Scan C# code, dependencies, secrets, and infrastructure in pull requests.
Corgea AI SAST helps teams catch C# code-level vulnerabilities early, while Corgea dependency scanning helps prioritize vulnerable NuGet packages.
1. Validate ASP.NET Core inputs on the server
Model binding is convenient, but binding is not the same as validation. Treat request bodies, query strings, route parameters, headers, cookies, and uploaded files as untrusted.
public sealed record CreateUserRequest(
[property: Required, EmailAddress, MaxLength(320)] string Email,
[property: Required, MaxLength(80)] string DisplayName
);
app.MapPost("/users", async (
CreateUserRequest request,
IValidator<CreateUserRequest> validator) =>
{
var result = await validator.ValidateAsync(request);
if (!result.IsValid)
{
return Results.BadRequest("Invalid request");
}
return Results.Created("/users/123", new { id = "123" });
});
Validation should be explicit about length, type, format, ranges, and allowed values. This is especially important for identifiers, file names, redirect URLs, and enum-like values.
2. Prevent SQL injection with EF Core and parameters
Most C# teams use Entity Framework Core, Dapper, or ADO.NET. All can be safe when values are parameterized. All can become risky when raw SQL is assembled with strings.
// Safer: LINQ expression becomes parameterized SQL.
var invoice = await db.Invoices
.Where(i => i.Id == invoiceId && i.OrganizationId == organizationId)
.SingleOrDefaultAsync();
Avoid this pattern:
// Unsafe: user input changes query syntax.
var sql = $"SELECT * FROM Users WHERE Email = '{email}'";
If raw SQL is unavoidable, bind parameters:
var user = await db.Users
.FromSqlInterpolated($"SELECT * FROM Users WHERE Email = {email}")
.SingleOrDefaultAsync();
For the vulnerability mechanics, see Corgea’s SQL injection guide.
3. Enforce authorization close to resource access
Do not stop at [Authorize]. That attribute verifies identity or policy, but object-level authorization still needs to check whether the authenticated user can access a specific tenant, record, file, or operation.
[Authorize]
[HttpGet("/invoices/{invoiceId:guid}")]
public async Task<IActionResult> GetInvoice(Guid invoiceId)
{
var invoice = await db.Invoices.FindAsync(invoiceId);
if (invoice is null || invoice.OrganizationId != CurrentUser.OrganizationId)
{
return NotFound();
}
return Ok(invoice);
}
Policy-based authorization is useful for coarse-grained rules. Resource checks are still needed for multi-tenant boundaries and IDOR prevention.
Corgea attack surface mapping helps connect reachable endpoints to code paths so missing authorization checks get prioritized by exposure.
4. Configure ASP.NET Core authentication and cookies safely
For cookie-backed apps:
- Set
Cookie.HttpOnly = true. - Set
Cookie.SecurePolicy = CookieSecurePolicy.Always. - Use
SameSite=LaxorStrictwhen compatible with the flow. - Rotate security stamps after password changes.
- Keep session and signing keys in a managed key store.
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "__Host-app";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
options.SlidingExpiration = true;
});
For APIs using bearer tokens, validate issuer, audience, signature, expiration, and key rotation. Do not trust arbitrary JWTs simply because they decode correctly.
5. Avoid unsafe deserialization
Deserialization bugs in .NET often come from accepting attacker-controlled data into overly powerful serializers or type binders. Safer defaults:
- Prefer
System.Text.Jsonfor JSON. - Avoid polymorphic deserialization from untrusted input unless types are tightly allowlisted.
- Do not deserialize untrusted binary formats.
- Do not deserialize directly into privileged domain objects when a narrow DTO is safer.
public sealed record UpdateProfileRequest(string DisplayName);
var request = JsonSerializer.Deserialize<UpdateProfileRequest>(
body,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = false
});
Keep request DTOs small. Treat deserialization as a boundary where validation, authorization, and business rules still need to run.
6. Prevent XSS and CSRF in ASP.NET Core views
Razor encodes output by default, but teams can bypass that protection with Html.Raw, unsafe JavaScript interpolation, or user-generated HTML.
Avoid rendering untrusted HTML. If rich text is a requirement, sanitize it with a dedicated allowlist sanitizer and store the sanitized result separately from raw input.
For browser-based apps that use cookies, keep CSRF defenses enabled. ASP.NET Core antiforgery tokens should protect state-changing form posts and sensitive actions.
7. Use proven cryptography and protect keys
The safest cryptography code is usually the code you do not write yourself. Use platform primitives and managed services:
- Use
RandomNumberGeneratorfor secure randomness. - Use
PasswordHasher<TUser>or a modern password hashing library for passwords. - Use TLS and managed KMS/HSM services for key storage.
- Avoid homegrown encryption formats.
- Plan rotation for signing and encryption keys.
Weak cryptography is a common static analysis finding. Corgea AI SAST helps surface unsafe algorithms, insecure random usage, and risky key handling patterns during review.
8. Keep secrets out of C# projects
Do not commit secrets in appsettings.json, test config, Dockerfiles, CI variables printed to logs, or connection string examples.
Use:
- .NET user secrets for local development.
- Cloud secret managers or vaults for production.
- Managed identity where possible.
- Corgea secrets scanning in pull requests to catch accidental leaks.
Configuration should make insecure defaults hard to deploy. A placeholder like ChangeMe123! has a habit of becoming production state.
9. Manage NuGet dependency risk
NuGet packages can introduce vulnerable transitive dependencies, abandoned packages, or risky build-time behavior.
Practical controls:
- Remove unused packages.
- Prefer maintained packages with clear provenance.
- Review major upgrades and packages with build targets.
- Keep package sources scoped and trusted.
- Monitor advisories and prioritize packages used by reachable code.
Corgea dependency scanning helps C# teams focus on package risk that matters instead of forcing every advisory into the same queue.
10. Scan C# code in pull requests and CI/CD
C# secure coding is easier to sustain when security feedback appears where developers already work. Combine:
- SAST for C# code and unsafe framework usage.
- Dependency scanning for NuGet risk.
- Secrets scanning for credentials.
- IaC and container scanning for deployment artifacts.
- PR comments with clear remediation guidance.
For broader pipeline rollout, read how to integrate static analysis into CI/CD.
Try Corgea scanning for C# and .NET
Want to enforce C# security best practices without turning every review into a manual checklist? Try Corgea for C# and .NET:
- Corgea AI SAST scans C# code for injection, authorization, unsafe data flow, and insecure API usage.
- Corgea dependency scanning helps prioritize vulnerable NuGet packages.
- Corgea secrets scanning catches leaked credentials before they spread.
- Corgea developer experience brings findings and remediation guidance into PR and IDE workflows.
Try Corgea today or book a custom demo for your C# applications.
If you are comparing platforms for .NET AppSec, see Corgea vs Checkmarx, Corgea vs Snyk, and Corgea vs GitHub Advanced Security. Teams that rely heavily on custom rules should also review Corgea vs Semgrep.
FAQ
What are the core C# security best practices?
Core C# security best practices include validating inputs, using parameterized data access, enforcing authorization on server-side resources, avoiding unsafe deserialization, protecting secrets, using proven cryptography APIs, and scanning code and NuGet dependencies before merge.
How should ASP.NET Core applications prevent SQL injection?
Use Entity Framework Core LINQ queries or parameterized commands, avoid string-concatenated SQL, validate input types before querying, and review any raw SQL paths carefully.
How do C# teams manage NuGet dependency risk?
C# teams should keep lockfiles where appropriate, remove unused packages, review package provenance, monitor advisories, and use dependency scanning to prioritize vulnerable packages that matter to the application.
Can Corgea scan C# and .NET applications?
Yes. Corgea AI SAST scans C# and .NET code for code-level vulnerabilities, and Corgea dependency scanning helps prioritize vulnerable NuGet packages in pull requests and CI/CD.