Skip to content

Commit 3bbee37

Browse files
committed
Support disabling antiforgery
1 parent 642d7af commit 3bbee37

File tree

8 files changed

+92
-14
lines changed

8 files changed

+92
-14
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
namespace Microsoft.AspNetCore.Antiforgery.Infrastructure;
5+
6+
/// <summary>
7+
/// A marker interface which can be used to identify antiforgery metadata.
8+
/// </summary>
9+
public interface IAntiforgeryMetadata
10+
{
11+
/// <summary>
12+
/// Gets a value indicating whether the antiforgery token should be validated.
13+
/// </summary>
14+
bool Required { get; }
15+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Antiforgery.Infrastructure.IAntiforgeryMetadata
3+
Microsoft.AspNetCore.Antiforgery.Infrastructure.IAntiforgeryMetadata.Required.get -> bool
4+
Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute
5+
Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute.RequireAntiforgeryTokenAttribute(bool required = true) -> void
6+
Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute.Required.get -> bool
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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.Infrastructure;
5+
6+
namespace Microsoft.AspNetCore.Antiforgery;
7+
8+
/// <summary>
9+
/// An attribute that can be used to indicate whether the antiforgery token must be validated.
10+
/// </summary>
11+
/// <param name="required">A value indicating whether the antiforgery token should be validated.</param>
12+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
13+
public class RequireAntiforgeryTokenAttribute(bool required = true) : Attribute, IAntiforgeryMetadata
14+
{
15+
/// <summary>
16+
/// Gets or sets a value indicating whether the antiforgery token should be validated.
17+
/// </summary>
18+
/// <remarks>
19+
/// Defaults to <see langword="true"/>; <see langword="false"/> indicates that
20+
/// the validation check for the antiforgery token can be avoided.
21+
/// </remarks>
22+
public bool Required { get; } = required;
23+
}

src/Components/Components/src/Rendering/RenderTreeBuilder.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -972,19 +972,11 @@ public void Dispose()
972972
// Walk backwards to find pairs of onsubmit and @onsubmit:name
973973
// and stop the first time we find an element or component frame.
974974
ref var attributeFrame = ref _entries.Buffer[j];
975-
if (attributeFrame.FrameType == RenderTreeFrameType.Component)
975+
if (attributeFrame.FrameType != RenderTreeFrameType.Attribute)
976976
{
977-
// If we were processing a component, ignore the values
978-
// as this feature is only for HTML elements.
979-
submitHanderNameIndex = -1;
980-
submitHandlerIndex = -1;
981-
break;
982-
}
983-
else if (attributeFrame.FrameType == RenderTreeFrameType.Element)
984-
{
985-
// We are at the end of the elements sequence
986977
break;
987978
}
979+
988980
if (string.Equals(attributeFrame.AttributeName, "onsubmit", StringComparison.Ordinal))
989981
{
990982
submitHandlerIndex = j;

src/Components/Endpoints/src/Builder/RazorComponentEndpointFactory.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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;
45
using Microsoft.AspNetCore.Builder;
56
using Microsoft.AspNetCore.Components.Discovery;
67
using Microsoft.AspNetCore.Http;
@@ -30,6 +31,9 @@ internal void AddEndpoints(
3031
RoutePatternFactory.Parse(pageDefinition.Route),
3132
order: 0);
3233

34+
// Require antiforgery by default, let the page override it.
35+
builder.Metadata.Add(new RequireAntiforgeryTokenAttribute());
36+
3337
// All attributes defined for the type are included as metadata.
3438
foreach (var attribute in pageDefinition.Metadata)
3539
{

src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Text;
88
using System.Text.Encodings.Web;
99
using Microsoft.AspNetCore.Antiforgery;
10+
using Microsoft.AspNetCore.Antiforgery.Infrastructure;
1011
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.WebUtilities;
1213
using Microsoft.Extensions.DependencyInjection;
@@ -38,8 +39,12 @@ private async Task RenderComponentCore()
3839
_context.Response.ContentType = RazorComponentResultExecutor.DefaultContentType;
3940
_renderer.InitializeStreamingRenderingFraming(_context);
4041

42+
// Metadata controls whether we require antiforgery protection for this endpoint or we should skip it.
43+
// The default for razor component endpoints is to require the metadata, but it can be overriden by
44+
// the developer.
45+
var antiforgeryMetadata = _context.GetEndpoint()!.Metadata.GetMetadata<IAntiforgeryMetadata>();
4146
var antiforgery = _context.RequestServices.GetRequiredService<IAntiforgery>();
42-
var (valid, isPost, handler) = await ValidateRequestAsync(antiforgery);
47+
var (valid, isPost, handler) = await ValidateRequestAsync(antiforgeryMetadata?.Required == true ? antiforgery : null);
4348
if (!valid)
4449
{
4550
// If the request is not valid we've already set the response to a 400 or similar
@@ -51,7 +56,7 @@ private async Task RenderComponentCore()
5156
{
5257
// Generate the antiforgery tokens before we start streaming the response, as it needs
5358
// to set the cookie header.
54-
antiforgery.GetAndStoreTokens(_context);
59+
antiforgery!.GetAndStoreTokens(_context);
5560
return Task.CompletedTask;
5661
});
5762

@@ -101,12 +106,12 @@ await EndpointHtmlRenderer.InitializeStandardComponentServicesAsync(
101106
await writer.FlushAsync();
102107
}
103108

104-
private async Task<RequestValidationState> ValidateRequestAsync(IAntiforgery antiforgery)
109+
private async Task<RequestValidationState> ValidateRequestAsync(IAntiforgery? antiforgery)
105110
{
106111
var isPost = HttpMethods.IsPost(_context.Request.Method);
107112
if (isPost)
108113
{
109-
var valid = await antiforgery.IsRequestValidAsync(_context);
114+
var valid = antiforgery == null || await antiforgery.IsRequestValidAsync(_context);
110115
if (!valid)
111116
{
112117
_context.Response.StatusCode = StatusCodes.Status400BadRequest;

src/Components/test/E2ETest/ServerRenderingTests/FormHandlingTests/FormWithParentBindingContextTest.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,21 @@ public void FormNoAntiforgeryReturnBadRequest(bool suppressEnhancedNavigation)
712712
DispatchToFormCore(dispatchToForm);
713713
}
714714

715+
[Theory]
716+
[InlineData(true)]
717+
[InlineData(false)]
718+
public void FormAntiforgeryCheckDisabledOnPage(bool suppressEnhancedNavigation)
719+
{
720+
var dispatchToForm = new DispatchToForm(this)
721+
{
722+
Url = "forms/disable-antiforgery-check",
723+
FormCssSelector = "form",
724+
ExpectedActionValue = null,
725+
SuppressEnhancedNavigation = suppressEnhancedNavigation,
726+
};
727+
DispatchToFormCore(dispatchToForm);
728+
}
729+
715730
[Fact]
716731
public void FormCanAddAntiforgeryAfterTheResponseHasStarted()
717732
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@page "/forms/disable-antiforgery-check"
2+
@attribute [RequireAntiforgeryToken(false)]
3+
@using Microsoft.AspNetCore.Antiforgery;
4+
@using Microsoft.AspNetCore.Components.Forms
5+
6+
<h2>Default form</h2>
7+
8+
<ActionForm OnSubmit="() => _submitted = true" >
9+
<input id="send" type="submit" value="Send" />
10+
</ActionForm>
11+
12+
@if (_submitted)
13+
{
14+
<p id="pass">Form submitted!</p>
15+
}
16+
17+
@code{
18+
bool _submitted = false;
19+
}

0 commit comments

Comments
 (0)