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

ASP.NET Core JWT bearer authentication: authentication vs authorization explained

Authentication: who you are.

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: Authentication vs authorization. How does JWT bearer authentication work in
// ASP.NET Core?
//
// - Authentication: who you are. The result is a ClaimsPrincipal (User).
// - Authorization: what you are allowed to do, using roles, policies and
//   ownership checks (Q36).
// Flow: the client obtains an access token from the Identity Provider (Entra ID,
// Keycloak, Auth0). The API validates the signature with the IdP's public keys
// (JWKS, from /.well-known/openid-configuration), and also checks iss, aud and
// exp. Validation is stateless: no database call is needed per request.
// - A JWT is signed, not encrypted. Anyone can read it (base64), so we never put
//   sensitive data in it.
// - Revocation: a JWT cannot easily be "cancelled". That is why access tokens
//   are short-lived (5-15 minutes) and renewed with refresh tokens using
//   rotation.
// - SPA: no tokens in localStorage, because an XSS attack can steal them. Better
//   is the BFF pattern (Backend-for-Frontend): the server holds the tokens and
//   the browser only has an HttpOnly, Secure, SameSite cookie.
// - Don't write your own authentication server: use an existing IdP or ASP.NET
//   Core Identity.
// ======================================================================

// JWT bearer authentication setup
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o =>
    {
        o.Authority = builder.Configuration["Auth:Authority"];   // e.g. https://login.microsoftonline.com/{tenant}/v2.0
        o.Audience  = builder.Configuration["Auth:Audience"];    // e.g. api://shop-api
        o.MapInboundClaims = false;                              // keeps the original claim names (sub, roles)
        o.TokenValidationParameters = new()
        {
            ValidateIssuer = true,
            ValidateAudience = true,                             // a token issued for another API is rejected
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(1)                  // default: 5 minutes
        };
    });
// With Entra ID: builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);

app.UseAuthentication();
app.UseAuthorization();
ASP.NET Core Interview Q&A

Securing webhooks in ASP.NET Core Minimal APIs: HMAC signature check and replay protection

Verify the HMAC signature with a constant-time comparison, plus a timestamp (replay protection).

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
// ======================================================================
// Q: Webhook from the logistics provider: what do you check?
//
// Verify the HMAC signature with a constant-time comparison, plus a timestamp
// (replay protection). Dedupe by event id. Reply quickly with 202 and process
// asynchronously.
// ======================================================================

app.MapPost("/webhooks/shipping", async (HttpRequest req, IOptions<WebhookOptions> opt, ...) =>
{
    using var reader = new StreamReader(req.Body);
    var body = await reader.ReadToEndAsync();
    var signature = req.Headers["X-Signature"].ToString();

    var expected = Convert.ToHexString(
        HMACSHA256.HashData(Encoding.UTF8.GetBytes(opt.Value.Secret), Encoding.UTF8.GetBytes(body)));

    if (!CryptographicOperations.FixedTimeEquals(
            Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature.ToUpperInvariant())))
        return Results.Unauthorized();

    // dedupe by event id, write to a queue, fast 202
    return Results.Accepted();
});
ASP.NET Core Interview Q&A

Calling flaky HTTP APIs in .NET: IHttpClientFactory with Polly v8 standard resilience handler

IHttpClientFactory: avoids the socket exhaustion of new HttpClient() on every call.

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
// ======================================================================
// Q: How do you call a flaky third-party HTTP API?
//
// - IHttpClientFactory: avoids the socket exhaustion of new HttpClient() on
//   every call.
// - Retry only on transient errors (5xx, 408, 429) and only on idempotent calls.
//   A POST without an idempotency key is not retried blindly.
// - Circuit breaker, so we don't overwhelm an ERP that is already struggling.
// - A typed client with our own DTOs = anti-corruption layer: the ERP's model
//   does not leak into our domain.
// ======================================================================

builder.Services.AddHttpClient<IErpClient, ErpClient>(c =>
{
    c.BaseAddress = new Uri(builder.Configuration["Erp:BaseUrl"]!);
    c.Timeout = Timeout.InfiniteTimeSpan; // the timeout is controlled by the resilience pipeline
})
.AddStandardResilienceHandler(o =>         // Microsoft.Extensions.Http.Resilience (Polly v8)
{
    o.Retry.MaxRetryAttempts = 3;
    o.Retry.UseJitter = true;
    o.AttemptTimeout.Timeout = TimeSpan.FromSeconds(10);
    o.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
});
ASP.NET Core Interview Q&A

Reliable ERP integration in ASP.NET Core: transactional outbox pattern with EF Core

Transactional Outbox: the order and the message for the ERP are written in the same transaction.

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
// ======================================================================
// Q: Our shop must send orders to the ERP. The ERP is sometimes down. Design it.
//
// Transactional Outbox: the order and the message for the ERP are written in the
// same transaction. A background worker sends the message with retries.
// The worker reads unsent messages (PostgreSQL: FOR UPDATE SKIP LOCKED, so
// multiple instances don't pick up the same one), sends them and marks them as
// processed. After N failures: dead-letter, alert, manual replay. Alternatively,
// Azure Service Bus with its built-in DLQ.
// Key point: idempotency. With retries the ERP may receive the same message
// twice. Send an Idempotency-Key or the OrderNumber as an external reference so
// the ERP rejects duplicates.
// ======================================================================

// Save the order and the outbox message in one transaction
await using var tx = await _db.Database.BeginTransactionAsync(ct);
_db.Orders.Add(order);
_db.Outbox.Add(new OutboxMessage
{
    Id = Guid.NewGuid(),
    Type = "OrderCreated",
    Payload = JsonSerializer.Serialize(new { order.Id, order.OrderNumber }),
    CreatedAt = DateTime.UtcNow
});
await _db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
ASP.NET Core Interview Q&A

ASP.NET Core error handling: ProblemDetails (RFC 9457) with a global IExceptionHandler

With ProblemDetails (RFC 9457) and a central IExceptionHandler.

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
// ======================================================================
// Q: How do you handle errors consistently in an API?
//
// With ProblemDetails (RFC 9457) and a central IExceptionHandler. Stack traces
// never reach the client.
// ======================================================================

builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

public class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> log) : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(HttpContext ctx, Exception ex, CancellationToken ct)
    {
        var (status, title) = ex switch
        {
            ValidationException => (400, "Validation failed"),
            NotFoundException   => (404, "Not found"),
            _                   => (500, "Unexpected error")
        };
        if (status == 500) log.LogError(ex, "Unhandled exception");

        ctx.Response.StatusCode = status;
        await ctx.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = status, Title = title,
            Extensions = { ["traceId"] = ctx.TraceIdentifier }
        }, ct);
        return true;
    }
}