Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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,143 @@
// 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!;

// 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,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, "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 _);
}
}
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 = "user@test.com",
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Member

Choose a reason for hiding this comment

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

Could you rename this to have .MicrosoftBenchmarks at the end

<IsMicrobenchmarksProject Condition=" $(MSBuildProjectName.EndsWith('.Microbenchmarks')) ">true</IsMicrobenchmarksProject>


<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<ServerGarbageCollection>true</ServerGarbageCollection>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TieredCompilation>false</TieredCompilation>
<DefineConstants>$(DefineConstants);IS_BENCHMARKS</DefineConstants>
<SkipMicrobenchmarksValidation>true</SkipMicrobenchmarksValidation>
<AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Reference Include="BenchmarkDotNet" />
<Reference Include="Microsoft.AspNetCore.Antiforgery" />
<Reference Include="Microsoft.AspNetCore.Http" />
<Reference Include="Microsoft.Extensions.DependencyInjection" />
<Reference Include="Microsoft.Extensions.Logging" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
</ItemGroup>

</Project>
Loading
Loading