diff --git a/src/Antiforgery/src/AntiforgeryOptions.cs b/src/Antiforgery/src/AntiforgeryOptions.cs
index e90aa356af66..f74dc51de3ad 100644
--- a/src/Antiforgery/src/AntiforgeryOptions.cs
+++ b/src/Antiforgery/src/AntiforgeryOptions.cs
@@ -81,4 +81,9 @@ public string FormFieldName
/// the X-Frame-Options header will not be generated for the response.
///
public bool SuppressXFrameOptionsHeader { get; set; }
+
+ ///
+ /// Specifies whether to suppress load of antiforgery token from request body.
+ ///
+ public bool SuppressReadingTokenFromFormBody { get; set; }
}
diff --git a/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs b/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs
index b57a40f162a6..b44e0e2da078 100644
--- a/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs
+++ b/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs
@@ -48,7 +48,7 @@ public async Task GetRequestTokensAsync(HttpContext httpCon
}
// Fall back to reading form instead
- if (requestToken.Count == 0 && httpContext.Request.HasFormContentType)
+ if (requestToken.Count == 0 && httpContext.Request.HasFormContentType && !_options.SuppressReadingTokenFromFormBody)
{
// Check the content-type before accessing the form collection to make sure
// we report errors gracefully.
diff --git a/src/Antiforgery/src/PublicAPI.Unshipped.txt b/src/Antiforgery/src/PublicAPI.Unshipped.txt
index 7dc5c58110bf..cdd99e7d264d 100644
--- a/src/Antiforgery/src/PublicAPI.Unshipped.txt
+++ b/src/Antiforgery/src/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+Microsoft.AspNetCore.Antiforgery.AntiforgeryOptions.SuppressReadingTokenFromFormBody.get -> bool
+Microsoft.AspNetCore.Antiforgery.AntiforgeryOptions.SuppressReadingTokenFromFormBody.set -> void
diff --git a/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs b/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs
index 6b431e9bbba1..65a3536d8051 100644
--- a/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs
+++ b/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs
@@ -122,6 +122,65 @@ public async Task GetRequestTokens_HeaderTokenTakensPriority_OverFormToken()
Assert.Equal("header-value", tokens.RequestToken);
}
+ [Fact]
+ public async Task GetRequestTokens_FormTokenDisabled_ReturnsNullToken()
+ {
+ // Arrange
+ var httpContext = GetHttpContext("cookie-name", "cookie-value");
+ httpContext.Request.ContentType = "application/x-www-form-urlencoded";
+ httpContext.Request.Form = new FormCollection(new Dictionary
+ {
+ { "form-field-name", "form-value" },
+ });
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ SuppressReadingTokenFromFormBody = true
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Equal("cookie-value", tokens.CookieToken);
+ Assert.Null(tokens.RequestToken);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_FormTokenDisabled_ReturnsHeaderToken()
+ {
+ // Arrange
+ var httpContext = GetHttpContext("cookie-name", "cookie-value");
+ httpContext.Request.ContentType = "application/x-www-form-urlencoded";
+ httpContext.Request.Form = new FormCollection(new Dictionary
+ {
+ { "form-field-name", "form-value" },
+ });
+ httpContext.Request.Headers.Add("header-name", "header-value");
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = "header-name",
+ SuppressReadingTokenFromFormBody = true
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act
+ var tokens = await tokenStore.GetRequestTokensAsync(httpContext);
+
+ // Assert
+ Assert.Equal("cookie-value", tokens.CookieToken);
+ Assert.Equal("header-value", tokens.RequestToken);
+ }
+
[Fact]
public async Task GetRequestTokens_NoHeaderToken_FallsBackToFormToken()
{