ASP.NET Core Interview Q&A

ASP.NET Core Interview Q&A

Explore asp.net core interview q&a code snippets and tutorials

ASP.NET Core Interview Q&A

Hardening a public ASP.NET Core API: rate limiting, CORS, HSTS, validation and headers

Input validation at the boundary: DataAnnotations or FluentValidation, size limits (MaxRequestBodySize, string length, number of rows), allowlists for enums and for file types in uploads.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// ======================================================================
// Q: How do you harden a public API?
//
// - Input validation at the boundary: DataAnnotations or FluentValidation, size
//   limits (MaxRequestBodySize, string length, number of rows), allowlists for
//   enums and for file types in uploads.
// - Security headers: Content-Security-Policy, X-Content-Type-Options: nosniff,
//   Referrer-Policy.
// - Errors: ProblemDetails without stack traces (Q6). Login responds identically
//   for "wrong user" and "wrong password" (user enumeration).
// - Audit log for sensitive actions: who approved which order, who changed a
//   credit limit.
// - Pipeline: SAST (CodeQL), secret scanning, dependency scanning (Q14), and
//   threat modeling (STRIDE) for new features.
// The AI angle: code from AI agents often forgets [Authorize], tenant filters,
// rate limiting and validation, and logs sensitive data. Those are the first
// things I check in review (Q1, Q2).
// ======================================================================

// Rate limiting, CORS, HSTS and middleware order
builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    o.AddPolicy("per-user", ctx => RateLimitPartition.GetFixedWindowLimiter(
        ctx.User.Identity?.Name ?? ctx.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
        _ => new FixedWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromMinutes(1) }));
});

builder.Services.AddCors(o => o.AddPolicy("shop", p => p
    .WithOrigins("https://shop.example.com")          // never AllowAnyOrigin together with credentials
    .WithMethods("GET", "POST")
    .AllowAnyHeader()
    .AllowCredentials()));

if (!app.Environment.IsDevelopment()) app.UseHsts();
app.UseHttpsRedirection();
app.UseCors("shop");
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();

app.MapPost("/api/auth/login", LoginHandler).AllowAnonymous().RequireRateLimiting("per-user");  // brute force
ASP.NET Core Interview Q&A

Storing passwords and protecting sensitive data in ASP.NET Core (Identity, Data Protection)

Passwords: hashing, not encryption.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ======================================================================
// Q: How do you store passwords and protect sensitive data?
//
// - Passwords: hashing, not encryption. A slow algorithm with a salt (PBKDF2,
//   bcrypt, Argon2). ASP.NET Core Identity already does this. Never MD5 or plain
//   SHA-256 on its own.
// - Even better: no passwords in our database at all. Login through an external
//   IdP (Entra ID, Entra External ID, Keycloak) with MFA.
// - In transit: TLS everywhere, HSTS. At rest: database encryption (TDE), and
//   for very sensitive columns Always Encrypted or application-level encryption.
// - Data Protection API for cookies and tokens in links (password reset,
//   unsubscribe). With multiple instances the keys must be shared.
// - PII in logs: never tokens, passwords or full card details. Redaction with
//   Microsoft.Extensions.Compliance.Redaction. GDPR: right to erasure and
//   retention policies.
// ======================================================================

// If you need your own hashing (without the whole of Identity)
var hasher = new PasswordHasher<User>();                       // PBKDF2 + salt, with a version in the hash
user.PasswordHash = hasher.HashPassword(user, password);

var result = hasher.VerifyHashedPassword(user, user.PasswordHash, attempt);
if (result == PasswordVerificationResult.SuccessRehashNeeded)  // old algorithm: rehash
    user.PasswordHash = hasher.HashPassword(user, attempt);

// Data Protection keys shared across all instances (otherwise cookies break after scale-out)
builder.Services.AddDataProtection()
    .PersistKeysToAzureBlobStorage(new Uri(cfg["DataProtection:BlobUri"]!), new DefaultAzureCredential())
    .ProtectKeysWithAzureKeyVault(new Uri(cfg["DataProtection:KeyUri"]!), new DefaultAzureCredential());
ASP.NET Core Interview Q&A

OWASP Top 10 in ASP.NET Core: what .NET protects you from and where to be careful

Broken access control: .NET provides [Authorize] and policies.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// ======================================================================
// Q: OWASP Top 10: what does .NET protect you from, and where must you be
// careful?
//
// - Broken access control: .NET provides [Authorize] and policies. Be careful
//   with IDOR (Q1), ownership checks and tenant filters.
// - SQL injection: LINQ / EF Core generates parameters. Be careful with
//   FromSqlRaw or ADO.NET with string concatenation.
// - XSS: Razor HTML-encodes output. Be careful with @Html.Raw, MarkupString in
//   Blazor, and set a Content-Security-Policy.
// - CSRF: antiforgery tokens in Razor forms. APIs with cookie auth need SameSite
//   and antiforgery. Bearer tokens in the header are not affected.
// - Mass assignment: model binding. Be careful with binding directly to an EF
//   entity instead of a DTO.
// - SSRF: nothing automatic. User-supplied URLs (e.g. a webhook URL) need an
//   allowlist and must not reach internal IPs.
// - Security misconfiguration: the templates give you HTTPS and HSTS. Be careful
//   with CORS AllowAnyOrigin, the developer exception page in production, and
//   secrets in appsettings.json.
// - Vulnerable components: dotnet list package --vulnerable, NuGet audit. Use
//   Dependabot / Renovate and keep the runtime and base images updated.
// ======================================================================

// SQL injection
db.Products.FromSqlRaw($"SELECT * FROM products WHERE name = '{name}'");   // WRONG
db.Products.FromSql($"SELECT * FROM products WHERE name = {name}");        // RIGHT: becomes a parameter

// Mass assignment / overposting
// WRONG: the client can also send "creditLimit": 1000000
public async Task<IActionResult> Update(Customer customer)
{ db.Update(customer); await db.SaveChangesAsync(); return Ok(); }

// RIGHT: a DTO with only the fields that are allowed to change
public record UpdateCustomerRequest(string Name, string Phone);
ASP.NET Core Interview Q&A

ASP.NET Core authorization: roles, claims, policies, resource-based checks, EF Core tenant filters

Resource-based authorization is for when the decision depends on the object itself (which customer the order belongs to).

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ======================================================================
// Q: Roles, claims, policies: how do you implement authorization?
//
// Resource-based authorization is for when the decision depends on the object
// itself (which customer the order belongs to).
// Multi-tenancy: a global query filter in EF Core, so that no query "forgets"
// the tenant.
// Roles for coarse-grained checks ("who can approve"), policies and claims for
// rules, resource-based authorization for ownership. And always an integration
// test that verifies customer A cannot see customer B's data (Q13).
// ======================================================================

// Policies and a secure-by-default fallback policy
builder.Services.AddAuthorizationBuilder()
    // Secure by default: every endpoint requires login unless it has [AllowAnonymous]
    .SetFallbackPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())
    .AddPolicy("ApproveOrders", p => p.RequireRole("PurchasingManager"))
    .AddPolicy("B2BCustomer",   p => p.RequireClaim("customer_id"));

[Authorize(Policy = "ApproveOrders")]
[HttpPost("{id:int}/approve")]
public async Task<IActionResult> Approve(int id, CancellationToken ct) { /* ... */ }

// Resource-based authorization: the order must belong to the caller's customer
public record SameCustomerRequirement : IAuthorizationRequirement;

public class SameCustomerHandler : AuthorizationHandler<SameCustomerRequirement, Order>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext ctx, SameCustomerRequirement req, Order order)
    {
        if (ctx.User.GetCustomerId() == order.CustomerId) ctx.Succeed(req);
        return Task.CompletedTask;
    }
}
builder.Services.AddSingleton<IAuthorizationHandler, SameCustomerHandler>();

// In the endpoint
var auth = await authorizationService.AuthorizeAsync(User, order, new SameCustomerRequirement());
if (!auth.Succeeded) return NotFound();                 // 404, not 403 (Q1)

// Multi-tenancy: EF Core global query filter per tenant
public class AppDbContext(DbContextOptions<AppDbContext> o, ITenantContext tenant) : DbContext(o)
{
    protected override void OnModelCreating(ModelBuilder mb) =>
        mb.Entity<Order>().HasQueryFilter(x => x.TenantId == tenant.TenantId);
}
// tenant.TenantId comes from the token's claims, NEVER from a header or query string
ASP.NET Core Interview Q&A

OAuth 2.0 vs OpenID Connect in .NET: choosing a flow, client credentials with MSAL

OAuth 2.0 is about authorization: it issues an access token for accessing an API.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ======================================================================
// Q: OAuth 2.0 vs OpenID Connect. Which flow do you use when?
//
// OAuth 2.0 is about authorization: it issues an access token for accessing an
// API. OpenID Connect builds on top of it and adds authentication: the id_token
// says who the user is.
// - Authorization Code + PKCE: users (web apps, SPA, mobile). The default today.
// - Client Credentials: service-to-service without a user: background jobs,
//   integration with Salesforce or an ERP.
// - Device Code: devices without a browser (CLI, IoT, e-mobility chargers).
// - Implicit, Password (ROPC): deprecated, we don't use them.
// Between our own services in Azure: Managed Identity, with no secret at all.
// ======================================================================

// Client credentials to an external API, with token caching (MSAL keeps a cache, so singleton)
builder.Services.AddSingleton(sp =>
{
    var o = builder.Configuration.GetSection("Erp").Get<ErpAuthOptions>()!;
    return ConfidentialClientApplicationBuilder.Create(o.ClientId)
        .WithClientSecret(o.ClientSecret)              // from Key Vault (Q15). Better: certificate
        .WithAuthority(o.Authority)
        .Build();
});

public class ErpTokenHandler(IConfidentialClientApplication msal, IOptions<ErpAuthOptions> opt) : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req, CancellationToken ct)
    {
        var result = await msal.AcquireTokenForClient([opt.Value.Scope]).ExecuteAsync(ct);   // from cache if still valid
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
        return await base.SendAsync(req, ct);
    }
}
builder.Services.AddTransient<ErpTokenHandler>();
builder.Services.AddHttpClient<IErpClient, ErpClient>().AddHttpMessageHandler<ErpTokenHandler>();