diff --git a/AspNetCore.slnx b/AspNetCore.slnx
index cf6f14abe556..03980d7b60a4 100644
--- a/AspNetCore.slnx
+++ b/AspNetCore.slnx
@@ -19,6 +19,9 @@
+
+
+
diff --git a/src/Antiforgery/Antiforgery.slnf b/src/Antiforgery/Antiforgery.slnf
index 7ce6a6ecf3dd..03f3fd7f5bc3 100644
--- a/src/Antiforgery/Antiforgery.slnf
+++ b/src/Antiforgery/Antiforgery.slnf
@@ -1,10 +1,11 @@
-{
+{
"solution": {
"path": "..\\..\\AspNetCore.slnx",
"projects": [
+ "src\\Antiforgery\\benchmarks\\Microsoft.AspNetCore.Antiforgery.Benchmarks\\Microsoft.AspNetCore.Antiforgery.Benchmarks.csproj",
"src\\Antiforgery\\samples\\MinimalFormSample\\MinimalFormSample.csproj",
"src\\Antiforgery\\src\\Microsoft.AspNetCore.Antiforgery.csproj",
"src\\Antiforgery\\test\\Microsoft.AspNetCore.Antiforgery.Test.csproj"
]
}
-}
+}
\ No newline at end of file
diff --git a/src/Antiforgery/benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks/Benchmarks/AntiforgeryBenchmarks.cs b/src/Antiforgery/benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks/Benchmarks/AntiforgeryBenchmarks.cs
new file mode 100644
index 000000000000..411ac3c51480
--- /dev/null
+++ b/src/Antiforgery/benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks/Benchmarks/AntiforgeryBenchmarks.cs
@@ -0,0 +1,165 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Security.Claims;
+using BenchmarkDotNet.Attributes;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Antiforgery.Benchmarks.Benchmarks;
+
+[AspNetCoreBenchmark]
+public class AntiforgeryBenchmarks
+{
+ private IServiceProvider _serviceProvider = null!;
+ private IAntiforgery _antiforgery = null!;
+ private string _cookieName = null!;
+ private string _formFieldName = null!;
+
+ // Reusable contexts - reset between iterations instead of recreating
+ private DefaultHttpContext _getAndStoreTokensContext = null!;
+ private DefaultHttpContext _validateRequestContext = null!;
+ private TestHttpResponseFeature _getAndStoreTokensResponseFeature = null!;
+
+ // Pre-generated tokens for validation benchmark
+ private string _cookieToken = null!;
+ private string _requestToken = null!;
+
+ // Pre-allocated form collection for validation benchmark
+ private FormCollection _validationFormCollection = null!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddAntiforgery();
+ serviceCollection.AddLogging();
+ _serviceProvider = serviceCollection.BuildServiceProvider();
+
+ _antiforgery = _serviceProvider.GetRequiredService();
+
+ // Get the actual cookie and form field names from options
+ var options = _serviceProvider.GetRequiredService>().Value;
+ _cookieName = options.Cookie.Name!;
+ _formFieldName = options.FormFieldName;
+
+ // Create reusable context for GetAndStoreTokens
+ _getAndStoreTokensResponseFeature = new TestHttpResponseFeature();
+ _getAndStoreTokensContext = CreateHttpContext(_getAndStoreTokensResponseFeature);
+
+ // Generate tokens for validation benchmark
+ var tokenContext = CreateHttpContext(new TestHttpResponseFeature());
+ var tokenSet = _antiforgery.GetAndStoreTokens(tokenContext);
+ _cookieToken = tokenSet.CookieToken!;
+ _requestToken = tokenSet.RequestToken!;
+
+ // Pre-allocate form collection for validation
+ _validationFormCollection = new FormCollection(new Dictionary
+ {
+ { _formFieldName, _requestToken }
+ });
+
+ // Create reusable context for ValidateRequestAsync
+ _validateRequestContext = CreateHttpContextWithTokens();
+ }
+
+ [IterationSetup(Target = nameof(GetAndStoreTokens))]
+ public void SetupGetAndStoreTokens()
+ {
+ // Reset the context instead of creating a new one
+ ResetHttpContextForGetAndStoreTokens();
+ }
+
+ [IterationSetup(Target = nameof(ValidateRequestAsync))]
+ public void SetupValidateRequest()
+ {
+ // Reset the context instead of creating a new one
+ ResetHttpContextForValidation();
+ }
+
+ [Benchmark]
+ public AntiforgeryTokenSet GetAndStoreTokens()
+ {
+ return _antiforgery.GetAndStoreTokens(_getAndStoreTokensContext);
+ }
+
+ [Benchmark]
+ public Task ValidateRequestAsync()
+ {
+ return _antiforgery.ValidateRequestAsync(_validateRequestContext);
+ }
+
+ private DefaultHttpContext CreateHttpContext(TestHttpResponseFeature responseFeature)
+ {
+ var context = new DefaultHttpContext();
+ context.RequestServices = _serviceProvider;
+
+ // Create an authenticated identity with a Name claim (required by antiforgery)
+ var identity = new ClaimsIdentity(
+ [new Claim(ClaimsIdentity.DefaultNameClaimType, "testuser@example.com")],
+ "TestAuth");
+ context.User = new ClaimsPrincipal(identity);
+
+ context.Request.Method = "POST";
+ context.Request.ContentType = "application/x-www-form-urlencoded";
+
+ // Setup response features to allow cookie writing
+ context.Features.Set(responseFeature);
+ context.Features.Set(new StreamResponseBodyFeature(Stream.Null));
+
+ return context;
+ }
+
+ private DefaultHttpContext CreateHttpContextWithTokens()
+ {
+ var context = new DefaultHttpContext();
+ context.RequestServices = _serviceProvider;
+
+ // Create an authenticated identity with a Name claim (required by antiforgery)
+ var identity = new ClaimsIdentity(
+ [new Claim(ClaimsIdentity.DefaultNameClaimType, "testuser@example.com")],
+ "TestAuth");
+ context.User = new ClaimsPrincipal(identity);
+
+ context.Request.Method = "POST";
+ context.Request.ContentType = "application/x-www-form-urlencoded";
+
+ // Set the cookie token using the actual cookie name from options
+ context.Request.Headers.Cookie = $"{_cookieName}={_cookieToken}";
+
+ // Set the request token in form using the pre-allocated form collection
+ context.Request.Form = _validationFormCollection;
+
+ return context;
+ }
+
+ private void ResetHttpContextForGetAndStoreTokens()
+ {
+ // Clear the antiforgery feature so it generates fresh tokens
+ _getAndStoreTokensContext.Features.Set(null);
+
+ // Reset response headers that antiforgery sets
+ _getAndStoreTokensResponseFeature.Headers.Clear();
+ }
+
+ private void ResetHttpContextForValidation()
+ {
+ // Clear the antiforgery feature so it deserializes tokens fresh
+ _validateRequestContext.Features.Set(null);
+ }
+
+ private sealed class TestHttpResponseFeature : IHttpResponseFeature
+ {
+ public int StatusCode { get; set; } = 200;
+ public string? ReasonPhrase { get; set; }
+ public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
+ public Stream Body { get; set; } = Stream.Null;
+ public bool HasStarted => false;
+
+ public void OnStarting(Func