Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AspNetCore.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<Project Path="src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery.csproj" />
<Project Path="src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj" />
</Folder>
<Folder Name="/src/Antiforgery/benchmarks/">
<Project Path="src/Antiforgery/benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks.csproj" Id="1dd9d6d8-44f5-4cc9-886d-ebfec9ec289e" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Project Path="src/Antiforgery/benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks.csproj" Id="1dd9d6d8-44f5-4cc9-886d-ebfec9ec289e" />
<Project Path="src/Antiforgery/benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks/Microsoft.AspNetCore.Antiforgery.Benchmarks.csproj" />

</Folder>
<Folder Name="/src/Azure/" />
<Folder Name="/src/Azure/AzureAppServices.HostingStartup/">
<Project Path="src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj" />
Expand Down
5 changes: 3 additions & 2 deletions src/Antiforgery/Antiforgery.slnf
Original file line number Diff line number Diff line change
@@ -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"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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;

/*

main branch:
| Method | Mean | Error | StdDev | Op/s | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------------- |---------:|---------:|---------:|---------:|------:|------:|------:|----------:|
| GetAndStoreTokens | 59.56 us | 2.482 us | 7.082 us | 16,789.6 | - | - | - | 5 KB |
| ValidateRequestAsync | 50.60 us | 2.150 us | 6.167 us | 19,764.1 | - | - | - | 4 KB |

this PR:
| Method | Mean | Error | StdDev | Op/s | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------------- |---------:|---------:|---------:|---------:|------:|------:|------:|----------:|
| GetAndStoreTokens | 49.62 us | 1.386 us | 3.954 us | 20,153.9 | - | - | - | 3 KB |
| ValidateRequestAsync | 43.67 us | 1.541 us | 4.471 us | 22,900.6 | - | - | - | 3 KB |

*/

[AspNetCoreBenchmark]
public class AntiforgeryBenchmarks
{
private IServiceProvider _serviceProvider = null!;
private IAntiforgery _antiforgery = null!;
private string _cookieName = null!;
private string _formFieldName = null!;

// For GetAndStoreTokens - fresh context each time
private HttpContext _getAndStoreTokensContext = null!;

// For ValidateRequestAsync - context with valid tokens
private HttpContext _validateRequestContext = null!;
private string _cookieToken = null!;
private string _requestToken = null!;

[GlobalSetup]
public void Setup()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddAntiforgery();
serviceCollection.AddLogging();
_serviceProvider = serviceCollection.BuildServiceProvider();

_antiforgery = _serviceProvider.GetRequiredService<IAntiforgery>();

// Get the actual cookie and form field names from options
var options = _serviceProvider.GetRequiredService<IOptions<AntiforgeryOptions>>().Value;
_cookieName = options.Cookie.Name!;
_formFieldName = options.FormFieldName;

// Setup context for GetAndStoreTokens (no existing tokens)
_getAndStoreTokensContext = CreateHttpContext();

// Generate tokens for validation benchmark
var tokenContext = CreateHttpContext();
var tokenSet = _antiforgery.GetAndStoreTokens(tokenContext);
_cookieToken = tokenSet.CookieToken!;
_requestToken = tokenSet.RequestToken!;

// Setup context for ValidateRequestAsync (with valid tokens)
_validateRequestContext = CreateHttpContextWithTokens(_cookieToken, _requestToken);
}

[IterationSetup(Target = nameof(GetAndStoreTokens))]
public void SetupGetAndStoreTokens()
{
// Create a fresh context for each iteration to simulate real-world usage
_getAndStoreTokensContext = CreateHttpContext();
}

[IterationSetup(Target = nameof(ValidateRequestAsync))]
public void SetupValidateRequest()
{
// Create a fresh context with tokens for each iteration
_validateRequestContext = CreateHttpContextWithTokens(_cookieToken, _requestToken);
}

[Benchmark]
public AntiforgeryTokenSet GetAndStoreTokens()
{
return _antiforgery.GetAndStoreTokens(_getAndStoreTokensContext);
}

[Benchmark]
public Task ValidateRequestAsync()
{
return _antiforgery.ValidateRequestAsync(_validateRequestContext);
}

private HttpContext CreateHttpContext()
{
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
var responseFeature = new TestHttpResponseFeature();
context.Features.Set<IHttpResponseFeature>(responseFeature);
context.Features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));

return context;
}

private HttpContext CreateHttpContextWithTokens(string cookieToken, string requestToken)
{
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 actual form field name
context.Request.Form = new FormCollection(new Dictionary<string, StringValues>
{
{ _formFieldName, requestToken }
});

return context;
}

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<object, Task> callback, object state) { }
public void OnCompleted(Func<object, Task> callback, object state) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// 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.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Antiforgery.Benchmarks.Benchmarks;

/*
main branch:
| Method | Mean | Error | StdDev | Op/s | Gen 0 | Gen 1 | Gen 2 | Allocated |
|----------------------------------- |------------:|----------:|----------:|----------------:|-------:|------:|------:|----------:|
| GenerateRequestToken_Anonymous | 11.0555 ns | 0.1203 ns | 0.1066 ns | 90,452,434.9 | 0.0007 | - | - | 56 B |
| GenerateRequestToken_Authenticated | 401.2545 ns | 7.1693 ns | 6.3554 ns | 2,492,184.2 | 0.0076 | - | - | 592 B |
| TryValidateTokenSet_Anonymous | 6.7227 ns | 0.0357 ns | 0.0316 ns | 148,750,552.9 | - | - | - | - |
| TryValidateTokenSet_Authenticated | 508.1742 ns | 4.4728 ns | 3.7350 ns | 1,967,829.1 | 0.0095 | - | - | 760 B |
| TryValidateTokenSet_ClaimsBased | 308.4674 ns | 3.3256 ns | 3.1108 ns | 3,241,833.1 | 0.0038 | - | - | 312 B |

this PR:
| Method | Mean | Error | StdDev | Op/s | Gen 0 | Gen 1 | Gen 2 | Allocated |
|----------------------------------- |------------:|----------:|-----------:|--------------:|-------:|------:|------:|----------:|
| GenerateRequestToken_Anonymous | 11.190 ns | 0.2428 ns | 0.6046 ns | 89,364,681.9 | 0.0007 | - | - | 56 B |
| GenerateRequestToken_Authenticated | 338.056 ns | 6.7313 ns | 14.9161 ns | 2,958,092.2 | 0.0052 | - | - | 424 B |
| TryValidateTokenSet_Anonymous | 7.966 ns | 0.1616 ns | 0.2915 ns | 125,531,038.3 | - | - | - | - |
| TryValidateTokenSet_Authenticated | 13.386 ns | 0.2476 ns | 0.3550 ns | 74,707,554.5 | - | - | - | - |
| TryValidateTokenSet_ClaimsBased | 220.111 ns | 4.2723 ns | 5.7034 ns | 4,543,156.3 | 0.0014 | - | - | 120 B |

*/

[AspNetCoreBenchmark]
public class AntiforgeryTokenGeneratorBenchmarks
{
private IAntiforgeryTokenGenerator _tokenGenerator = null!;

// Anonymous user scenario
private HttpContext _anonymousHttpContext = null!;
private AntiforgeryToken _anonymousCookieToken = null!;
private AntiforgeryToken _anonymousRequestToken = null!;

// Authenticated user with username scenario
private HttpContext _authenticatedHttpContext = null!;
private AntiforgeryToken _authenticatedCookieToken = null!;
private AntiforgeryToken _authenticatedRequestToken = null!;

// Claims-based user scenario
private HttpContext _claimsHttpContext = null!;
private AntiforgeryToken _claimsCookieToken = null!;
private AntiforgeryToken _claimsRequestToken = null!;

[GlobalSetup]
public void Setup()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddAntiforgery();
var serviceProvider = serviceCollection.BuildServiceProvider();

_tokenGenerator = serviceProvider.GetRequiredService<IAntiforgeryTokenGenerator>();

// Setup anonymous user scenario
_anonymousHttpContext = new DefaultHttpContext();
_anonymousHttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());

_anonymousCookieToken = new AntiforgeryToken { IsCookieToken = true };
_anonymousRequestToken = new AntiforgeryToken
{
IsCookieToken = false,
SecurityToken = _anonymousCookieToken.SecurityToken,
Username = string.Empty
};

// Setup authenticated user with username scenario
_authenticatedHttpContext = new DefaultHttpContext();
var authenticatedIdentity = new ClaimsIdentity(
[new Claim(ClaimsIdentity.DefaultNameClaimType, "testuser@example.com")],
"TestAuthentication");
_authenticatedHttpContext.User = new ClaimsPrincipal(authenticatedIdentity);

_authenticatedCookieToken = new AntiforgeryToken { IsCookieToken = true };
_authenticatedRequestToken = new AntiforgeryToken
{
IsCookieToken = false,
SecurityToken = _authenticatedCookieToken.SecurityToken,
Username = "testuser@example.com"
};

// Setup claims-based user scenario
_claimsHttpContext = new DefaultHttpContext();
var claimsIdentity = new ClaimsIdentity(
[
new Claim(ClaimsIdentity.DefaultNameClaimType, "claimsuser@example.com"),
new Claim("sub", "user-id-12345"),
new Claim(ClaimTypes.NameIdentifier, "unique-id")
],
"ClaimsAuthentication");
_claimsHttpContext.User = new ClaimsPrincipal(claimsIdentity);

_claimsCookieToken = new AntiforgeryToken { IsCookieToken = true };

// For claims-based users, we need to extract the ClaimUid
var claimUid = new byte[32];
_ = new DefaultClaimUidExtractor().TryExtractClaimUidBytes(_claimsHttpContext.User, claimUid);
_claimsRequestToken = new AntiforgeryToken
{
IsCookieToken = false,
SecurityToken = _claimsCookieToken.SecurityToken,
ClaimUid = claimUid is not null ? new BinaryBlob(256, claimUid) : null
};
}

[Benchmark]
public object GenerateRequestToken_Anonymous()
{
return _tokenGenerator.GenerateRequestToken(_anonymousHttpContext, _anonymousCookieToken);
}

[Benchmark]
public object GenerateRequestToken_Authenticated()
{
return _tokenGenerator.GenerateRequestToken(_authenticatedHttpContext, _authenticatedCookieToken);
}

[Benchmark]
public bool TryValidateTokenSet_Anonymous()
{
return _tokenGenerator.TryValidateTokenSet(
_anonymousHttpContext,
_anonymousCookieToken,
_anonymousRequestToken,
out _);
}

[Benchmark]
public bool TryValidateTokenSet_Authenticated()
{
return _tokenGenerator.TryValidateTokenSet(
_authenticatedHttpContext,
_authenticatedCookieToken,
_authenticatedRequestToken,
out _);
}

[Benchmark]
public bool TryValidateTokenSet_ClaimsBased()
{
return _tokenGenerator.TryValidateTokenSet(
_claimsHttpContext,
_claimsCookieToken,
_claimsRequestToken,
out _);
}
}
Loading
Loading