From 6b7447d256b3ee4a8834fefb8e17c126b46b8f4b Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 10 Jul 2025 12:51:13 +0200 Subject: [PATCH 1/4] Emit antiforgery only when streaming --- .../Endpoints/src/RazorComponentEndpointInvoker.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 8e5338f54788..e617d707ba5f 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -76,14 +76,6 @@ private async Task RenderComponentCore(HttpContext context) return; } - context.Response.OnStarting(() => - { - // Generate the antiforgery tokens before we start streaming the response, as it needs - // to set the cookie header. - antiforgery!.GetAndStoreTokens(context); - return Task.CompletedTask; - }); - if (httpActivityContext != default) { _activityLinkStore.SetActivityContext(ComponentsActivityLinkStore.Http, httpActivityContext, null); @@ -154,6 +146,8 @@ await _renderer.InitializeStandardComponentServicesAsync( if (!quiesceTask.IsCompletedSuccessfully) { + // We need to ensure that the antiforgery tokens are generated and stored in the response + antiforgery!.GetAndStoreTokens(context); await _renderer.SendStreamingUpdatesAsync(context, quiesceTask, bufferWriter); } else From 22aeaa082777f9bdc03b90500cc5b81bbf597bb9 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 10 Jul 2025 13:54:37 +0200 Subject: [PATCH 2/4] Emit antiforgery only when streaming --- .../Forms/EndpointAntiforgeryStateProvider.cs | 24 ++++++++++++------- .../src/RazorComponentEndpointInvoker.cs | 13 ++++++++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/Components/Endpoints/src/Forms/EndpointAntiforgeryStateProvider.cs b/src/Components/Endpoints/src/Forms/EndpointAntiforgeryStateProvider.cs index b1c113beca88..8a0a00df13d1 100644 --- a/src/Components/Endpoints/src/Forms/EndpointAntiforgeryStateProvider.cs +++ b/src/Components/Endpoints/src/Forms/EndpointAntiforgeryStateProvider.cs @@ -10,10 +10,12 @@ namespace Microsoft.AspNetCore.Components.Endpoints.Forms; internal class EndpointAntiforgeryStateProvider(IAntiforgery antiforgery) : DefaultAntiforgeryStateProvider() { private HttpContext? _context; + private bool _canGenerateToken; internal void SetRequestContext(HttpContext context) { _context = context; + _canGenerateToken = true; } public override AntiforgeryRequestToken? GetAntiforgeryToken() @@ -24,17 +26,23 @@ internal void SetRequestContext(HttpContext context) return _currentToken; } - // We already have a callback setup to generate the token when the response starts if needed. - // If we need the tokens before we start streaming the response, we'll generate and store them; - // otherwise we'll just retrieve them. - // In case there are no tokens available, we are going to return null and no-op. - var tokens = !_context.Response.HasStarted ? antiforgery.GetAndStoreTokens(_context) : antiforgery.GetTokens(_context); - if (tokens.RequestToken is null) + if (_currentToken == null && _canGenerateToken) { - return null; + // We already have a callback setup to generate the token when the response starts if needed. + // If we need the tokens before we start streaming the response, we'll generate and store them; + // otherwise we'll just retrieve them. + // In case there are no tokens available, we are going to return null and no-op. + var tokens = !_context.Response.HasStarted ? antiforgery.GetAndStoreTokens(_context) : antiforgery.GetTokens(_context); + if (tokens.RequestToken is null) + { + return null; + } + + _currentToken = new AntiforgeryRequestToken(tokens.RequestToken, tokens.FormFieldName); } - _currentToken = new AntiforgeryRequestToken(tokens.RequestToken, tokens.FormFieldName); return _currentToken; } + + internal void DisableTokenGeneration() => _canGenerateToken = false; } diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index e617d707ba5f..ff8071b59564 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -6,7 +6,9 @@ using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Antiforgery; +using Microsoft.AspNetCore.Components.Endpoints.Forms; using Microsoft.AspNetCore.Components.Endpoints.Rendering; +using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Infrastructure; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; @@ -135,8 +137,17 @@ await _renderer.InitializeStandardComponentServicesAsync( var bufferingFeature = context.Features.GetRequiredFeature(); bufferingFeature.DisableBuffering(); + // Store the tokens if not emitted already in case we stream a form in the response. + antiforgery!.GetAndStoreTokens(context); + context.Response.Headers.ContentEncoding = "identity"; } + else + { + // Disable token generation on EndpointAntiforgeryStateProvider if we are not streaming. + var provider = (EndpointAntiforgeryStateProvider)context.RequestServices.GetRequiredService(); + provider.DisableTokenGeneration(); + } // Importantly, we must not yield this thread (which holds exclusive access to the renderer sync context) // in between the first call to htmlContent.WriteTo and the point where we start listening for subsequent @@ -146,8 +157,6 @@ await _renderer.InitializeStandardComponentServicesAsync( if (!quiesceTask.IsCompletedSuccessfully) { - // We need to ensure that the antiforgery tokens are generated and stored in the response - antiforgery!.GetAndStoreTokens(context); await _renderer.SendStreamingUpdatesAsync(context, quiesceTask, bufferWriter); } else From a0e652ea9b6b8122af6a38e9e3c6c590fd99e8ad Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 10 Jul 2025 15:17:57 +0200 Subject: [PATCH 3/4] Emit antiforgery also when render modes are configured --- src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index ff8071b59564..1a5b03bdddfb 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; +using System.Net.Http; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Antiforgery; @@ -142,7 +143,7 @@ await _renderer.InitializeStandardComponentServicesAsync( context.Response.Headers.ContentEncoding = "identity"; } - else + else if (endpoint.Metadata.GetMetadata()?.ConfiguredRenderModes.Length == 0) { // Disable token generation on EndpointAntiforgeryStateProvider if we are not streaming. var provider = (EndpointAntiforgeryStateProvider)context.RequestServices.GetRequiredService(); From d388d9bb1328c2b89c641211aff5939c8d23f082 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 10 Jul 2025 15:28:33 +0200 Subject: [PATCH 4/4] Fix build --- src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs index 1a5b03bdddfb..6e9bb59ab45a 100644 --- a/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs +++ b/src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Diagnostics; -using System.Net.Http; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Antiforgery;