- #Security
- #Software Architecture
- #ASP.NET Core
- #Azure
- #DevSecOps
- #.NET
Security Should Be Part of Architecture, Not a Final Step
A practical guide to security-by-design in ASP.NET Core and Azure, covering identity, authorization, secrets, validation, rate limiting, encryption, network isolation, least privilege, and DevSecOps.
On this page
A common anti-pattern in software engineering is treating security as an afterthought: build the application, deploy it to the cloud, and ask the security team to run a vulnerability scan the week before launch.
That audit-at-the-end approach can catch known vulnerabilities and missing patches, but it is not the same as designing a resilient, secure system.
Security decisions fundamentally alter your architecture from day one.
Retrofitting network isolation,
zero-trust token propagation,
secretless managed identities, or least-privilege RBAC
onto an already-deployed distributed system is orders of magnitude more
expensive than building them into the foundation.
Foundational architectural decisions dictate your entire attack surface:
- Identity & Access: Who can access the system, and how are credentials verified?
- Granular Authorization: What specific actions can each caller execute?
- Public Surface Area: Which endpoints and ports are publicly exposed?
- Secret Management: Where do database keys and API tokens live?
- Network Boundaries: Which microservices and datastores are allowed to communicate?
- Data Protection: How is sensitive data protected in transit and at rest?
- Telemetry & Blast Radius: What happens if a single component or credential is compromised?
Microsoft's Azure Well-Architected Framework recommends embedding security throughout the workload lifecycle rather than treating it as an isolated gate at the end. The goal is not to declare an application "impossible to hack." The goal is to minimize attack surfaces, enforce least privilege, constrain the blast radius, and make malicious activity immediately detectable and remediable.
Replacing public database ports and over-privileged subscription credentials with private network isolation and scoped managed identities ensures that even a compromised web container cannot pivot to other resources or delete infrastructure.
1. Security Starts With Architecture
A naive, basic application deployment often looks like this:
While straightforward to build and run locally, this design leaves critical questions unanswered: What prevents brute-force credential stuffing? What stops SQL injection or volumetric DDoS attacks? What happens if the database port is scanned on the public internet?
A production-ready cloud architecture establishes defense-in-depth through multi-layered controls:
Security controls must align with the workload's specific threat model,
regulatory requirements (SOC2, HIPAA,
PCI-DSS), data classification, and cost profile. Incorporating
WAF inspection, API gateway policies,
network segmentation, and managed identities early
provides a scalable template.
2. Authentication and Authorization Are Distinct Concepts
One of the most foundational architectural separations is distinguishing identity verification from permission evaluation.
Authentication (AuthN)
Authentication answers the question: "Who are you?"
Microsoft Entra ID(Azure AD)OAuth 2.0/OpenID Connect(OIDC)- Client Certificates (
mTLS) API Keys/Workload Identity Federation
Authorization (AuthZ)
Authorization answers the question: "What are you allowed to do?"
A valid token does not grant universal access. The application must enforce explicit policies based on roles, claims, or fine-grained scopes:
ASP.NET Core Implementation
In modern ASP.NET Core, configure JWT Bearer authentication and explicit policy-based authorization in Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = builder.Configuration["Authentication:Authority"];
options.Audience = builder.Configuration["Authentication:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
RequireSignedTokens = true,
ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 },
ClockSkew = TimeSpan.FromSeconds(30)
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Orders.Read", policy =>
policy.RequireClaim("scope", "orders.read"));
options.AddPolicy("Orders.Write", policy =>
policy.RequireClaim("scope", "orders.write"));
options.AddPolicy("Orders.Admin", policy =>
policy.RequireRole("PlatformAdmin"));
});Protect endpoints declaratively:
[Authorize(Policy = "Orders.Read")]
[HttpGet("/orders")]
public IActionResult GetOrders()
{
return Ok(new[] { new { Id = 1, Item = "Laptop" } });
}
[Authorize(Policy = "Orders.Admin")]
[HttpDelete("/orders/{id:int}")]
public IActionResult DeleteOrder(int id)
{
return NoContent();
}Authenticate at the edge or gateway, but always authorize at the domain boundary.
Never allow an authenticated identity to perform actions without validating its permissions against the target resource.
Never trust the algorithm claimed by the token itself. Explicitly allowlist
ValidAlgorithms and set RequireSignedTokens to
true—otherwise a crafted token using an unexpected algorithm
(such as none or a symmetric key mismatched with an asymmetric key) can
bypass signature verification entirely.
3. Secrets Should Never Live in Source Code
Hardcoded passwords, API keys, and connection strings committed to Git repositories remain one of the most common vectors for security incidents.
Avoid placing production secrets directly inside configuration files:
// ❌ ANTI-PATTERN: Never commit secrets to source control
{
"ConnectionStrings": {
"Database": "Host=prod-db.postgres.database.azure.com;Username=admin;Password=SuperSecretPassword123!"
},
"PaymentGateway": {
"ApiKey": "sk_live_94829384729103847"
}
}Non-sensitive configuration (such as base URLs, timeouts, and feature flags) belongs in configuration files:
// ✅ SAFE: Non-secret operational settings
{
"ExternalServices": {
"PaymentApi": {
"BaseUrl": "https://api.stripe.com",
"TimeoutSeconds": 30
}
}
}The Azure Managed Identity Approach
In cloud-native Azure environments, eliminate credentials entirely by using System-Assigned or User-Assigned Managed Identities:
Clean C# Integration with Azure SDK
Using Azure.Identity and DefaultAzureCredential, your code functions identically in local development (using developer credentials) and in Azure (using Managed Identity):
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
// Resolves Managed Identity in Azure, CLI/VS credentials locally
var credential = new DefaultAzureCredential();
var secretClient = new SecretClient(
new Uri("https://kv-production-ecommerce.vault.azure.net/"),
credential);
KeyVaultSecret secret = await secretClient.GetSecretAsync("StripeApiKey");
string apiKey = secret.Value;Managed Identity removes the need for your application to hold or rotate a
static credential, which eliminates an entire class of leaks
(Git commits, log dumps, config drift). It does not eliminate trust: the
Managed Identity's RBAC role assignment and Microsoft Entra
ID's token issuance are still the boundary an attacker would target. Scope
those roles as tightly as the secrets they replace.
4. Input Validation Is an Architectural Boundary
Authentication confirms identity, but it does not make input trustworthy. An authenticated employee or customer can still submit malformed payloads, excessive string lengths, or malicious parameters.
Multi-Layered Validation in ASP.NET Core
Combine strong schema validation at the HTTP boundary with domain validation in the application core:
using System.ComponentModel.DataAnnotations;
public sealed record CreateOrderRequest(
[Required, StringLength(50, MinimumLength = 3)]
string CustomerId,
[Range(1, 100, ErrorMessage = "Quantity must be between 1 and 100.")]
int Quantity,
[Required, RegularExpression(@"^[A-Z0-9-]{6,12}$", ErrorMessage = "Invalid SKU format.")]
string ProductSku
);Attribute validation is your first filter, but it must be supplemented with
domain rule checks (e.g., FluentValidation or
Rich Domain Aggregates) to prevent logical corruption.
5. Rate Limiting for Availability and Abuse Prevention
An authenticated user or third-party service can still overwhelm your database through automated loops, scraping, or unintentional concurrency bugs.
ASP.NET Core Native Rate Limiting
ASP.NET Core provides built-in rate-limiting middleware:
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddFixedWindowLimiter("PublicApiPolicy", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 0;
});
options.AddSlidingWindowLimiter("AuthEndpointPolicy", opt =>
{
opt.PermitLimit = 10;
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 6;
opt.QueueLimit = 0;
});
});
var app = builder.Build();
app.UseRateLimiter();
app.MapGet("/products", () => Results.Ok(new { Status = "Available" }))
.RequireRateLimiting("PublicApiPolicy");AddFixedWindowLimiter and AddSlidingWindowLimiter track counters
in-process memory. The moment your API runs as more than one
replica—which the architecture in this article assumes—each instance
enforces its own independent limit, so the effective ceiling multiplies by
the replica count instead of staying fixed. For a real global limit, either
enforce throttling upstream at Azure API Management or
Azure Front Door, or back the .NET limiter with a distributed
store (e.g., a Redis-based partitioned limiter) so all instances share
state.
6. Encryption: In Transit and At Rest
Architectural design must guarantee confidentiality and integrity across the entire data lifecycle.
Never implement custom hashing or encryption algorithms. Leverage
battle-tested framework primitives (System.Security.Cryptography,
DataProtectionProvider) and managed cloud
encryption services.
7. Network Isolation & Defense in Depth
Relying solely on application-level authentication creates a single point of failure. If an attacker discovers an authorization bypass or a zero-day exploit, an exposed database port allows direct compromise.
- ✓Databases have no public IP addresses and sit behind Private Endpoints.
- ✓Application tier only communicates with APIM via Virtual Network integration.
- ✓
Network Security Groups(NSGs) restrict egress to approved subnets and endpoints. - ✓Microservice boundaries use
mTLSand private DNS zones.
- ✗Database exposed to 0.0.0.0/0 with password-only authentication
- ✗Backend microservices have public URLs accessible directly from the internet
- ✗Secrets and connection strings transmitted across unencrypted public networks
- ✗Compromising one container grants unrestricted horizontal network access
8. Preventing Gateway Bypass
When an API Gateway (such as Azure API Management) handles critical concerns—such as authentication, WAF filtering, SSL termination, and rate limiting—the backend services must never be directly reachable from the public internet.
Enforcing Ingress Restrictions in Azure
- Private Endpoints: Bind the backend App Service or Container App to a private virtual network.
- Access Restrictions: Restrict backend IP firewall rules to allow traffic only from the API Management subnet.
- Mutual TLS / Client Certificates: Configure backend APIs to require a verified client certificate issued by the gateway.
Enforce the certificate check in code rather than relying on network placement alone, so a misconfigured firewall rule doesn't become the only line of defense:
builder.Services
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes = CertificateTypes.Chained;
options.RevocationMode = X509RevocationMode.Online;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var thumbprint = context.ClientCertificate.Thumbprint;
if (thumbprint != builder.Configuration["Gateway:TrustedThumbprint"])
{
context.Fail("Untrusted client certificate.");
return Task.CompletedTask;
}
context.Success();
return Task.CompletedTask;
}
};
});
app.UseAuthentication();
app.MapGet("/internal/orders", () => Results.Ok())
.RequireAuthorization()
.RequireHost("*.internal.azurecontainerapps.io");9. The Principle of Least Privilege
Least privilege dictates that an identity (human,
service principal, or managed identity) receives
only the exact permissions required to complete its function—and nothing more.
Assigning roles like Owner or Contributor at the
Azure Subscription or Resource Group level to an application identity is a
severe anti-pattern. Always scope RBAC to the specific resource
(e.g., Key Vault Secrets User
scoped to a single vault).
10. Security Logging, Auditing, and Observability
A secure system must not only prevent unauthorized access; it must also generate actionable telemetry to answer forensic questions during an incident:
- Who accessed the resource?
- What operation was attempted?
- When and from what IP address did the request originate?
- Was access allowed or denied?
Sanitizing Log Output
Never log sensitive data:Scroll horizontally to see all columns.
| Logged Category | ❌ Prohibited in Logs | ✅ Recommended Sanitized Alternative |
|---|---|---|
| Authentication | Raw passwords, JWT signatures, Bearer tokens | Identity Subject ID (sub), Token Expiration, Issuer |
| PII & Privacy | Credit card PANs, SSNs, raw email addresses | Masked identifiers (**-**-****-4242), Hashed IDs |
| Infrastructure | Full DB connection strings with credentials | Target Server Name (prod-db-cluster), Database Name |
11. Dependency Security & DevSecOps Automation
Modern applications are constructed primarily from third-party open-source packages, container base images, and runtime dependencies. A vulnerability in an external library is an architectural vulnerability in your system.
Automated CLI Checks in CI/CD
Integrate automated dependency checks into your build pipeline:
# Scan .NET dependencies for known CVEs
dotnet list package --vulnerable --include-transitive
# Check for outdated packages with breaking changes
dotnet list package --outdated12. A Practical Production Azure Architecture
Bringing all these principles together yields a robust, scalable, and isolated cloud architecture:
Core Architecture Components:
- Azure Front Door + WAF: Enforces DDoS mitigation and Layer-7 web application firewall policies globally.
- Azure API Management: Acts as the single public entry point, validating tokens and throttling abusive callers.
- Private Virtual Network: Encloses the application tier, database, and key vault inside isolated subnets with no public internet ingress.
- Microsoft Entra ID & Managed Identities: Eliminates hardcoded passwords by generating dynamic tokens for datastore and key vault communication.
13. Comprehensive Security-by-Design Checklist
Before releasing an architecture to production, run through this architectural verification checklist:
1. Identity & Access Management
- Are all API endpoints secured with
OAuth 2.0/OpenID Connecttokens? - Is authorization policy-based rather than relying on role strings?
- Are workload identities and service principals audited regularly?
2. Network Isolation & Perimeter
- Are backend APIs and databases completely isolated from direct public internet access?
- Do microservices communicate over
private endpointsand virtual network links? - Is gateway bypass prevented via IP whitelisting or
mutual TLS?
3. Secrets & Configuration
- Are zero credentials or connection strings committed to source control?
- Are cloud resources utilizing
Managed Identitiesinstead of connection passwords? - Are secrets in
Azure Key Vaultrotated and audited automatically?
4. Input Validation & Resilience
- Is incoming payload schema and size validated before hitting domain logic?
- Are rate limits configured on public and expensive computational endpoints?
- Are all database interactions strictly parameterized to eliminate
injection vulnerabilities?
5. Data Encryption & Storage
- Is all external and internal traffic enforced over
TLS 1.3/ HTTPS? - Is
transparent data encryption(TDE) active on all relational and blob stores? - Are cryptographic keys managed via
Azure Key Vault HSMs?
6. Observability & DevSecOps
- Are automated
SAST, dependency vulnerability, and secret scans active inCI/CD? - Are security events and authorization failures routed to a
SIEM(e.g., Microsoft Sentinel)? - Is application telemetry sanitized to prevent leaking customer PII or auth credentials?
14. Security Is a Continuous Design Constraint
The fundamental mindset shift in modern engineering is changing the guiding question:
Instead of asking:
"How do we secure this application before going live?"
Architects must ask:
"What security properties, isolation boundaries, and trust constraints must this system guarantee before we write the first line of code?"
When security is embedded directly into the architecture, your application becomes inherently resilient, auditable, and prepared to adapt to evolving threats.
Related Architecture Deep Dives
- Clean Architecture in .NET 10: Beyond Folder Conventions to Strict Dependency Direction — Enforce strict isolation boundaries and compile-time dependency inversion using NetArchTest.
- The Dual-Write Trap: Production-Grade Transactional Outbox and Idempotent Consumers in .NET 10 — Eliminate distributed data loss and guarantee reliable, idempotent message processing across microservices.
- Observability in .NET 10: You Can't Fix What You Can't See — End-to-end distributed tracing, OpenTelemetry, and structured security telemetry.
Sources and Further Reading
- Microsoft Azure Well-Architected Framework — Security Pillar
- Microsoft Azure Architecture Center — Protect APIs with Azure Front Door and API Management
- Microsoft Entra ID Documentation — Managed Identities for Azure Resources
- OWASP Top 10 API Security Risks
- ASP.NET Core Security and Authorization Guidelines
Need an architectural security audit, cloud modernization advisory, or guidance structuring resilient .NET backends? Explore my background and advisory services or connect on LinkedIn.
Written by Rehan Akbar
Software Engineer · Singapore · .NET, Cloud & Applied AI
I write about the engineering trade-offs behind reliable production systems—Clean Architecture, distributed resilience, observability, and practical AI engineering. Open for architecture advisory and technical collaborations.
Clean Architecture in .NET 10: Beyond Folder Conventions to Strict Dependency Direction
Next ArticleFrom 50 Minutes to 90 Seconds: A Postmortem on Scaling a Bulk Data Export Feature
Discussion & Comments
Share your thoughts, ask technical questions, or discuss architecture edge cases.