|
1 | 1 | using Microsoft.AspNetCore.Antiforgery; |
| 2 | +using Microsoft.AspNetCore.Mvc; |
| 3 | +using Microsoft.Extensions.Logging; |
2 | 4 |
|
3 | 5 | var builder = WebApplication.CreateBuilder(args); |
4 | | -builder.Logging.ClearProviders(); |
5 | | -builder.Services.AddAntiforgery(options => options.HeaderName = "XSRF-TOKEN"); |
| 6 | + |
| 7 | +var debug = builder.Configuration.GetValue<bool>("debug"); |
| 8 | +if (debug) |
| 9 | +{ |
| 10 | + builder.Logging.AddFilter("Microsoft", LogLevel.Warning); |
| 11 | + builder.Logging.AddFilter("Microsoft.AspNetCore.Antiforgery.CsrfProtectionMiddleware", LogLevel.Debug); |
| 12 | +} |
| 13 | +else |
| 14 | +{ |
| 15 | + builder.Logging.ClearProviders(); |
| 16 | +} |
| 17 | + |
| 18 | +// "csrf" exercises the auto-injected cross-origin (Sec-Fetch) CSRF protection in isolation, |
| 19 | +// so the token-based antiforgery services/middleware are left out to avoid overriding its verdict. |
| 20 | +var scenario = builder.Configuration["scenario"] ?? "antiforgery"; |
| 21 | +var tokenAntiforgeryEnabled = !string.Equals(scenario, "csrf", StringComparison.OrdinalIgnoreCase); |
| 22 | +Console.WriteLine($"Scenario: '{scenario}'. Token-based antiforgery enabled: {tokenAntiforgeryEnabled}."); |
| 23 | + |
| 24 | +if (tokenAntiforgeryEnabled) |
| 25 | +{ |
| 26 | + builder.Services.AddAntiforgery(options => options.HeaderName = "XSRF-TOKEN"); |
| 27 | +} |
6 | 28 |
|
7 | 29 | var app = builder.Build(); |
8 | | -app.UseAntiforgery(); |
9 | 30 |
|
10 | | -app.MapGet("/", () => Results.Ok("hello world!")); |
11 | | -app.MapGet("/noOp", (HttpContext ctx, IAntiforgery antiforgery) => Results.Ok()); |
| 31 | +if (debug) |
| 32 | +{ |
| 33 | + var requestCount = 0; |
| 34 | + app.Use(async (context, next) => |
| 35 | + { |
| 36 | + var n = Interlocked.Increment(ref requestCount); |
| 37 | + if (n <= 20) |
| 38 | + { |
| 39 | + var request = context.Request; |
| 40 | + Console.WriteLine($"[debug] #{n} {request.Method} {request.Path}{request.QueryString}"); |
| 41 | + foreach (var header in request.Headers) |
| 42 | + { |
| 43 | + Console.WriteLine($"[debug] #{n} {header.Key}: {header.Value}"); |
| 44 | + } |
| 45 | + } |
12 | 46 |
|
13 | | -// GET https://localhost:55471/auth |
14 | | -app.MapGet("/auth", (HttpContext ctx, IAntiforgery antiforgery) => |
| 47 | + await next(context); |
| 48 | + |
| 49 | + if (n <= 20) |
| 50 | + { |
| 51 | + Console.WriteLine($"[debug] #{n} -> {context.Response.StatusCode}"); |
| 52 | + } |
| 53 | + }); |
| 54 | +} |
| 55 | + |
| 56 | +if (tokenAntiforgeryEnabled) |
15 | 57 | { |
16 | | - var token = antiforgery.GetAndStoreTokens(ctx); |
17 | | - ctx.Response.Headers.Append("XSRF-TOKEN", token.RequestToken!); |
18 | | - return Results.Ok(); |
19 | | -}); |
| 58 | + app.UseAntiforgery(); |
| 59 | +} |
| 60 | + |
| 61 | +app.MapGet("/", () => Results.Ok("hello world!")); |
20 | 62 |
|
21 | | -// POST https://localhost:55471/validateToken |
22 | | -app.MapPost("/validateToken", async (HttpContext ctx, IAntiforgery antiforgery) => |
| 63 | +// POST endpoint guarded only by the auto-injected cross-origin CSRF protection. |
| 64 | +app.MapPost("/csrf", ([FromForm] string name) => Results.Ok()); |
| 65 | + |
| 66 | +// Token-based antiforgery endpoints. These depend on IAntiforgery, which is only |
| 67 | +// registered when the token-based antiforgery services are added above. |
| 68 | +if (tokenAntiforgeryEnabled) |
23 | 69 | { |
24 | | - // HttpContext is expected to have 2 headers: |
25 | | - // 1) antiforgery token ("XSRF-TOKEN"); |
26 | | - // 2) cookie token ("Cookie") with value of `.AspNetCore.Antiforgery.<unique-sequence>=<cookie_header>` |
| 70 | + app.MapGet("/noOp", (HttpContext ctx, IAntiforgery antiforgery) => Results.Ok()); |
| 71 | + |
| 72 | + // GET https://localhost:55471/auth |
| 73 | + app.MapGet("/auth", (HttpContext ctx, IAntiforgery antiforgery) => |
| 74 | + { |
| 75 | + var token = antiforgery.GetAndStoreTokens(ctx); |
| 76 | + ctx.Response.Headers.Append("XSRF-TOKEN", token.RequestToken!); |
| 77 | + return Results.Ok(); |
| 78 | + }); |
| 79 | + |
| 80 | + // POST https://localhost:55471/validateToken |
| 81 | + app.MapPost("/validateToken", async (HttpContext ctx, IAntiforgery antiforgery) => |
| 82 | + { |
| 83 | + // HttpContext is expected to have 2 headers: |
| 84 | + // 1) antiforgery token ("XSRF-TOKEN"); |
| 85 | + // 2) cookie token ("Cookie") with value of `.AspNetCore.Antiforgery.<unique-sequence>=<cookie_header>` |
27 | 86 |
|
28 | | - await antiforgery.ValidateRequestAsync(ctx); |
29 | | - return Results.Ok(); |
30 | | -}); |
| 87 | + await antiforgery.ValidateRequestAsync(ctx); |
| 88 | + return Results.Ok(); |
| 89 | + }); |
| 90 | +} |
31 | 91 |
|
32 | 92 | await app.StartAsync(); |
33 | 93 | Console.WriteLine("Application started."); |
|
0 commit comments