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() {