Skip to content
Open
Show file tree
Hide file tree
Changes from all 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,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<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;

// 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<string, StringValues>
{
{ _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, "[email protected]")],
"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<IHttpResponseFeature>(responseFeature);
context.Features.Set<IHttpResponseBodyFeature>(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, "[email protected]")],
"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<IAntiforgeryFeature>(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<IAntiforgeryFeature>(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<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,132 @@
// 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;

[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, "[email protected]")],
"TestAuthentication");
_authenticatedHttpContext.User = new ClaimsPrincipal(authenticatedIdentity);

_authenticatedCookieToken = new AntiforgeryToken { IsCookieToken = true };
_authenticatedRequestToken = new AntiforgeryToken
{
IsCookieToken = false,
SecurityToken = _authenticatedCookieToken.SecurityToken,
Username = "[email protected]"
};

// Setup claims-based user scenario
_claimsHttpContext = new DefaultHttpContext();
var claimsIdentity = new ClaimsIdentity(
[
new Claim(ClaimsIdentity.DefaultNameClaimType, "[email protected]"),
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 _);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Antiforgery.Benchmarks.Benchmarks;

[AspNetCoreBenchmark]
public class AntiforgeryTokenSerializerBenchmarks
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
private IAntiforgeryTokenSerializer _tokenSerializer;

private AntiforgeryToken _token;
private string _serializedToken;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.

[GlobalSetup]
public void Setup()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddAntiforgery();
var serviceProvider = serviceCollection.BuildServiceProvider();
_tokenSerializer = serviceProvider.GetRequiredService<IAntiforgeryTokenSerializer>();

_token = new AntiforgeryToken()
{
IsCookieToken = false,
Username = "[email protected]",
ClaimUid = new BinaryBlob(AntiforgeryToken.ClaimUidBitLength),
AdditionalData = "additional-data-here"
};

_serializedToken = _tokenSerializer.Serialize(_token);
}

[Benchmark]
public string Serialize()
{
return _tokenSerializer.Serialize(_token);
}

[Benchmark]
public object Deserialize()
{
return _tokenSerializer.Deserialize(_serializedToken);
}
}
Loading
Loading