Skip to content

Commit 2067158

Browse files
[release/8.0-preview7] Add support for anti-forgery middleware (#49530)
* Add support for anti-forgery middleware * Address feedback * Clean up checks for valid HTTP methods * Remove unneeded using * Move RequestSizeLimit processing to EndpointRoutingMiddleware * Address more feedback --------- Co-authored-by: Safia Abdalla <[email protected]>
1 parent 6714ac8 commit 2067158

File tree

112 files changed

+2331
-276
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+2331
-276
lines changed

AspNetCore.sln

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.ApiEndpoints
17681768
EndProject
17691769
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components.WasmMinimal", "src\Components\test\testassets\Components.WasmMinimal\Components.WasmMinimal.csproj", "{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}"
17701770
EndProject
1771+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinimalFormSample", "src\Antiforgery\samples\MinimalFormSample\MinimalFormSample.csproj", "{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}"
1772+
EndProject
1773+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcFormSample", "src\Mvc\samples\MvcFormSample\MvcFormSample.csproj", "{055F86AA-FB37-40CC-B39E-C29CE7547BB7}"
1774+
EndProject
17711775
Global
17721776
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17731777
Debug|Any CPU = Debug|Any CPU
@@ -10609,6 +10613,38 @@ Global
1060910613
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|x64.Build.0 = Release|Any CPU
1061010614
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|x86.ActiveCfg = Release|Any CPU
1061110615
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3}.Release|x86.Build.0 = Release|Any CPU
10616+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10617+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|Any CPU.Build.0 = Debug|Any CPU
10618+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|arm64.ActiveCfg = Debug|Any CPU
10619+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|arm64.Build.0 = Debug|Any CPU
10620+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|x64.ActiveCfg = Debug|Any CPU
10621+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|x64.Build.0 = Debug|Any CPU
10622+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|x86.ActiveCfg = Debug|Any CPU
10623+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Debug|x86.Build.0 = Debug|Any CPU
10624+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|Any CPU.ActiveCfg = Release|Any CPU
10625+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|Any CPU.Build.0 = Release|Any CPU
10626+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|arm64.ActiveCfg = Release|Any CPU
10627+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|arm64.Build.0 = Release|Any CPU
10628+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|x64.ActiveCfg = Release|Any CPU
10629+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|x64.Build.0 = Release|Any CPU
10630+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|x86.ActiveCfg = Release|Any CPU
10631+
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A}.Release|x86.Build.0 = Release|Any CPU
10632+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10633+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
10634+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|arm64.ActiveCfg = Debug|Any CPU
10635+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|arm64.Build.0 = Debug|Any CPU
10636+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|x64.ActiveCfg = Debug|Any CPU
10637+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|x64.Build.0 = Debug|Any CPU
10638+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|x86.ActiveCfg = Debug|Any CPU
10639+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Debug|x86.Build.0 = Debug|Any CPU
10640+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
10641+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|Any CPU.Build.0 = Release|Any CPU
10642+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|arm64.ActiveCfg = Release|Any CPU
10643+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|arm64.Build.0 = Release|Any CPU
10644+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|x64.ActiveCfg = Release|Any CPU
10645+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|x64.Build.0 = Release|Any CPU
10646+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|x86.ActiveCfg = Release|Any CPU
10647+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|x86.Build.0 = Release|Any CPU
1061210648
EndGlobalSection
1061310649
GlobalSection(SolutionProperties) = preSolution
1061410650
HideSolutionNode = FALSE
@@ -11482,6 +11518,7 @@ Global
1148211518
{66FA1041-5556-43A0-9CA3-F9937F085F6E} = {56291265-B7BF-4756-92AB-FC30F09381D1}
1148311519
{37FC77EA-AC44-4D08-B002-8EFF415C424A} = {64B2A28F-6D82-4F2B-B0BB-88DE5216DD2C}
1148411520
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3} = {6126DCE4-9692-4EE2-B240-C65743572995}
11521+
{055F86AA-FB37-40CC-B39E-C29CE7547BB7} = {B8825E86-B8EA-4666-B681-C443D027C95D}
1148511522
EndGlobalSection
1148611523
GlobalSection(ExtensibilityGlobals) = postSolution
1148711524
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

src/Antiforgery/Antiforgery.slnf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"solution": {
33
"path": "..\\..\\AspNetCore.sln",
4-
"projects" : [
4+
"projects": [
5+
"src\\Antiforgery\\samples\\MinimalFormSample\\MinimalFormSample.csproj",
56
"src\\Antiforgery\\src\\Microsoft.AspNetCore.Antiforgery.csproj",
67
"src\\Antiforgery\\test\\Microsoft.AspNetCore.Antiforgery.Test.csproj"
78
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<RootNamespace>MinimalSample</RootNamespace>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Reference Include="Microsoft.AspNetCore" />
12+
<Reference Include="Microsoft.AspNetCore.Hosting" />
13+
<Reference Include="Microsoft.AspNetCore.Http" />
14+
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
15+
<Reference Include="Microsoft.AspNetCore.Http.Results" />
16+
<Reference Include="Microsoft.AspNetCore.Routing" />
17+
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
18+
<Reference Include="Microsoft.AspNetCore.Antiforgery" />
19+
<Reference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" />
20+
<Reference Include="Microsoft.AspNetCore.Mvc.Core" />
21+
<Reference Include="Microsoft.Net.Http.Headers" />
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Antiforgery;
5+
using Microsoft.AspNetCore.Mvc;
6+
7+
var builder = WebApplication.CreateBuilder(args);
8+
9+
builder.Services.AddAntiforgery();
10+
11+
var app = builder.Build();
12+
13+
app.UseAntiforgery();
14+
15+
app.MapGet("/antiforgery", (HttpContext context, IAntiforgery antiforgery) =>
16+
{
17+
var token = antiforgery.GetAndStoreTokens(context);
18+
var html = $"""
19+
<html>
20+
<body>
21+
<form action="/todo" method="POST" enctype="multipart/form-data">
22+
<input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}" />
23+
<input type="text" name="name" />
24+
<input type="date" name="dueDate" />
25+
<input type="checkbox" name="isCompleted" />
26+
<input type="submit" />
27+
</form>
28+
</body>
29+
</html>
30+
""";
31+
return Results.Content(html, "text/html");
32+
});
33+
34+
app.MapGet("/no-antiforgery", () =>
35+
{
36+
var html = """
37+
<html>
38+
<body>
39+
<form action="/todo" method="POST" enctype="multipart/form-data">
40+
<input type="text" name="name" />
41+
<input type="date" name="dueDate" />
42+
<input type="checkbox" name="isCompleted" />
43+
<input type="submit" />
44+
</form>
45+
</body>
46+
</html>
47+
""";
48+
return Results.Content(html, "text/html");
49+
});
50+
51+
app.MapPost("/todo", [ValidateAntiForgeryToken] ([FromForm] Todo todo) => Results.Ok(todo));
52+
53+
app.MapPost("/todo-raw", async context =>
54+
{
55+
var form = await context.Request.ReadFormAsync();
56+
var name = form["name"].ToString();
57+
var dueDate = DateTime.Parse(form["dueDate"].ToString());
58+
var isCompleted = bool.Parse(form["isCompleted"].ToString());
59+
var result = Results.Ok(new Todo(name, isCompleted, dueDate));
60+
await result.ExecuteAsync(context);
61+
}).WithMetadata(new AntiforgeryMetadata(true));
62+
63+
app.Run();
64+
65+
class Todo(string name, bool isCompleted, DateTime dueDate)
66+
{
67+
public string Name { get; set; } = name;
68+
public bool IsCompleted { get; set; } = isCompleted;
69+
public DateTime DueDate { get; set; } = dueDate;
70+
}
71+
72+
class AntiforgeryMetadata: IAntiforgeryMetadata
73+
{
74+
public AntiforgeryMetadata(bool required)
75+
{
76+
RequiresValidation = required;
77+
}
78+
79+
public bool RequiresValidation { get; }
80+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:37561",
8+
"sslPort": 44385
9+
}
10+
},
11+
"profiles": {
12+
"http": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"applicationUrl": "http://localhost:5092",
17+
"environmentVariables": {
18+
"ASPNETCORE_ENVIRONMENT": "Development"
19+
}
20+
},
21+
"https": {
22+
"commandName": "Project",
23+
"dotnetRunMessages": true,
24+
"launchBrowser": true,
25+
"applicationUrl": "https://localhost:7245;http://localhost:5092",
26+
"environmentVariables": {
27+
"ASPNETCORE_ENVIRONMENT": "Development"
28+
}
29+
},
30+
"IIS Express": {
31+
"commandName": "IISExpress",
32+
"launchBrowser": true,
33+
"environmentVariables": {
34+
"ASPNETCORE_ENVIRONMENT": "Development"
35+
}
36+
}
37+
}
38+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
},
8+
"AllowedHosts": "*"
9+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Antiforgery;
5+
using Microsoft.AspNetCore.Antiforgery.Internal;
6+
7+
namespace Microsoft.AspNetCore.Builder;
8+
9+
/// <summary>
10+
/// Anti-forgery extension methods for <see cref="IApplicationBuilder"/>.
11+
/// </summary>
12+
public static class AntiforgeryApplicationBuilderExtensions
13+
{
14+
private const string AntiforgeryMiddlewareSetKey = "__AntiforgeryMiddlewareSet";
15+
16+
/// <summary>
17+
/// Adds the anti-forgery middleware to the pipeline.
18+
/// </summary>
19+
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
20+
/// <returns>The app builder.</returns>
21+
public static IApplicationBuilder UseAntiforgery(this IApplicationBuilder builder)
22+
{
23+
ArgumentNullException.ThrowIfNull(builder);
24+
builder.VerifyAntiforgeryServicesAreRegistered();
25+
26+
builder.Properties[AntiforgeryMiddlewareSetKey] = true;
27+
builder.UseMiddleware<AntiforgeryMiddleware>();
28+
29+
return builder;
30+
}
31+
32+
private static void VerifyAntiforgeryServicesAreRegistered(this IApplicationBuilder builder)
33+
{
34+
if (builder.ApplicationServices.GetService(typeof(IAntiforgery)) == null)
35+
{
36+
throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddAntiforgery' in the application startup code.");
37+
}
38+
}
39+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace Microsoft.AspNetCore.Antiforgery.Internal;
7+
8+
internal sealed class AntiforgeryMiddleware(IAntiforgery antiforgery, RequestDelegate next)
9+
{
10+
private readonly RequestDelegate _next = next;
11+
private readonly IAntiforgery _antiforgery = antiforgery;
12+
13+
private const string AntiforgeryMiddlewareWithEndpointInvokedKey = "__AntiforgeryMiddlewareWithEndpointInvoked";
14+
private static readonly object AntiforgeryMiddlewareWithEndpointInvokedValue = new object();
15+
16+
public Task Invoke(HttpContext context)
17+
{
18+
var endpoint = context.GetEndpoint();
19+
20+
if (endpoint is not null)
21+
{
22+
context.Items[AntiforgeryMiddlewareWithEndpointInvokedKey] = AntiforgeryMiddlewareWithEndpointInvokedValue;
23+
}
24+
25+
var method = context.Request.Method;
26+
if (!HttpMethodExtensions.IsValidHttpMethodForForm(method))
27+
{
28+
return _next(context);
29+
}
30+
31+
if (endpoint?.Metadata.GetMetadata<IAntiforgeryMetadata>() is { RequiresValidation: true })
32+
{
33+
return InvokeAwaited(context);
34+
}
35+
36+
return _next(context);
37+
}
38+
39+
public async Task InvokeAwaited(HttpContext context)
40+
{
41+
try
42+
{
43+
await _antiforgery.ValidateRequestAsync(context);
44+
context.Features.Set(AntiforgeryValidationFeature.Valid);
45+
}
46+
catch (AntiforgeryValidationException e)
47+
{
48+
context.Features.Set<IAntiforgeryValidationFeature>(new AntiforgeryValidationFeature(false, e));
49+
}
50+
await _next(context);
51+
}
52+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
namespace Microsoft.AspNetCore.Antiforgery;
4+
5+
internal sealed class AntiforgeryValidationFeature(bool isValid, AntiforgeryValidationException? exception) : IAntiforgeryValidationFeature
6+
{
7+
public static readonly IAntiforgeryValidationFeature Valid = new AntiforgeryValidationFeature(true, null);
8+
9+
public bool IsValid { get; } = isValid;
10+
public Exception? Error { get; } = exception;
11+
}

src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<ItemGroup>
1414
<Reference Include="Microsoft.AspNetCore.DataProtection" />
1515
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
16+
<Reference Include="Microsoft.AspNetCore.Http.Features" />
1617
<Reference Include="Microsoft.AspNetCore.Http.Extensions" />
1718
<Reference Include="Microsoft.AspNetCore.WebUtilities" />
1819
<Reference Include="Microsoft.Extensions.ObjectPool" />
@@ -22,4 +23,8 @@
2223
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="$(MoqPublicKey)" />
2324
<InternalsVisibleTo Include="Microsoft.AspNetCore.Antiforgery.Test" />
2425
</ItemGroup>
26+
27+
<ItemGroup>
28+
<Compile Include="$(SharedSourceRoot)HttpMethodExtensions.cs" LinkBase="Shared"/>
29+
</ItemGroup>
2530
</Project>
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Antiforgery.Infrastructure.IAntiforgeryMetadata
3-
Microsoft.AspNetCore.Antiforgery.Infrastructure.IAntiforgeryMetadata.Required.get -> bool
42
Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute
53
Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute.RequireAntiforgeryTokenAttribute(bool required = true) -> void
6-
Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute.Required.get -> bool
4+
Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute.RequiresValidation.get -> bool
5+
Microsoft.AspNetCore.Builder.AntiforgeryApplicationBuilderExtensions
6+
static Microsoft.AspNetCore.Builder.AntiforgeryApplicationBuilderExtensions.UseAntiforgery(this Microsoft.AspNetCore.Builder.IApplicationBuilder! builder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!

src/Antiforgery/src/RequireAntiforgeryTokenAttribute.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using Microsoft.AspNetCore.Antiforgery.Infrastructure;
5-
64
namespace Microsoft.AspNetCore.Antiforgery;
75

86
/// <summary>
@@ -19,5 +17,5 @@ public class RequireAntiforgeryTokenAttribute(bool required = true) : Attribute,
1917
/// Defaults to <see langword="true"/>; <see langword="false"/> indicates that
2018
/// the validation check for the antiforgery token can be avoided.
2119
/// </remarks>
22-
public bool Required { get; } = required;
20+
public bool RequiresValidation { get; } = required;
2321
}

0 commit comments

Comments
 (0)