Skip to content

Commit 8b27376

Browse files
authored
[Blazor] Antiforgery fix (#50946)
# Ensure antiforgery token flows to Blazor WebAssembly The change makes sure that we persist the Antiforgery token during prerendering so that it is available to WebAssembly components. ## Description * When using cookie authentication it is necessary to use antiforgery protection to prevent cross-site request forgery attacks. * Blazor Webassembly interactive components must get access to the request antiforgery token to attach it to any outgoing API call. * The antiforgery request token was not flowing from the server to the client correctly. * This change enables calling APIs from web assembly to the server safely. Fixes #50900 ## Customer Impact .NET 8.0 customers who have created Blazor Web Apps will fail to call APIs from webassembly components, as they won't be able to attach the required antiforgery token. ## Regression? - [ ] Yes - [X] No [If yes, specify the version the behavior has regressed from] ## Risk - [ ] High - [ ] Medium - [X] Low The fix is simple and we added an E2E test to cover the scenario. ## Verification - [ ] Manual (required) - [X] Automated ## Packaging changes reviewed? - [ ] Yes - [ ] No - [X] N/A ---- ## When servicing release/2.1 - [ ] Make necessary changes in eng/PatchConfig.props
1 parent 0cbe08f commit 8b27376

File tree

6 files changed

+100
-2
lines changed

6 files changed

+100
-2
lines changed

src/Components/Shared/src/DefaultAntiforgeryStateProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public DefaultAntiforgeryStateProvider(PersistentComponentState state)
2424
// don't have access to the request.
2525
_subscription = state.RegisterOnPersisting(() =>
2626
{
27-
state.PersistAsJson(PersistenceKey, _currentToken);
27+
state.PersistAsJson(PersistenceKey, GetAntiforgeryToken());
2828
return Task.CompletedTask;
29-
}, new InteractiveAutoRenderMode());
29+
}, RenderMode.InteractiveWebAssembly);
3030

3131
state.TryTakeFromJson(PersistenceKey, out _currentToken);
3232
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,19 @@ public void FormNoAntiforgeryReturnBadRequest(bool suppressEnhancedNavigation)
896896
DispatchToFormCore(dispatchToForm);
897897
}
898898

899+
[Fact]
900+
public void CanUseAntiforgeryTokenInWasm()
901+
{
902+
var dispatchToForm = new DispatchToForm(this)
903+
{
904+
Url = "forms/antiforgery-wasm",
905+
FormCssSelector = "form",
906+
InputFieldId = "Value",
907+
SuppressEnhancedNavigation = true,
908+
};
909+
DispatchToFormCore(dispatchToForm);
910+
}
911+
899912
[Theory]
900913
[InlineData(true)]
901914
[InlineData(false)]

src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<Reference Include="Microsoft.AspNetCore.Authentication.Cookies" />
1515
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" />
1616
<Reference Include="Microsoft.AspNetCore.Components.Server" />
17+
<Reference Include="Microsoft.AspNetCore.Http.Results" />
1718
<Reference Include="Microsoft.AspNetCore.Cors" />
1819
<Reference Include="Microsoft.AspNetCore.Mvc" />
1920
<Reference Include="Microsoft.AspNetCore.Components.Server" />

src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Components.TestServer.RazorComponents.Pages.Forms;
1010
using Components.TestServer.Services;
1111
using Microsoft.AspNetCore.Components.WebAssembly.Server;
12+
using Microsoft.AspNetCore.Mvc;
1213

1314
namespace TestServer;
1415

@@ -154,6 +155,11 @@ private static void MapEnhancedNavigationEndpoints(IEndpointRouteBuilder endpoin
154155
await response.WriteAsync("<html><body><h1>This is a non-Blazor endpoint</h1><p>That's all</p></body></html>");
155156
});
156157

158+
endpoints.MapPost("api/antiforgery-form", ([FromForm] string value) =>
159+
{
160+
return Results.Ok(value);
161+
});
162+
157163
static Task PerformRedirection(HttpRequest request, HttpResponse response)
158164
{
159165
response.Redirect(request.Query["external"] == "true"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@page "/forms/antiforgery-wasm"
2+
<h3>FormRunningOnWasmCanUseAntiforgeryToken</h3>
3+
4+
<TestContentPackage.WasmFormComponent/>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
@attribute [RenderModeInteractiveWebAssembly]
2+
@using Microsoft.AspNetCore.Components.Forms
3+
@using System.Net.Http
4+
<h3>WasmFormComponent</h3>
5+
6+
<form @formname="WasmForm" @onsubmit="SubmitForm">
7+
<input id="Value" @bind="_value" name="Value" />
8+
@if (OperatingSystem.IsBrowser())
9+
{
10+
<input id="send" type="submit" value="Send" />
11+
}
12+
<AntiforgeryToken />
13+
</form>
14+
15+
@if (_posted)
16+
{
17+
@if (_succeeded)
18+
{
19+
<p id="pass">Posting the value succeded.</p>
20+
}
21+
else
22+
{
23+
<p>Posting the value failed.</p>
24+
}
25+
}
26+
else
27+
{
28+
<p>Antiforgery: @_token</p>
29+
}
30+
31+
@code {
32+
string _value;
33+
string _token;
34+
bool _succeeded;
35+
bool _posted;
36+
37+
[Inject] public AntiforgeryStateProvider AntiforgeryState { get; set; }
38+
[Inject] public NavigationManager Navigation { get; set; }
39+
40+
protected override void OnInitialized()
41+
{
42+
if (OperatingSystem.IsBrowser())
43+
{
44+
var antiforgery = AntiforgeryState.GetAntiforgeryToken();
45+
_token = antiforgery.Value;
46+
}
47+
}
48+
49+
private async Task SubmitForm()
50+
{
51+
if (OperatingSystem.IsBrowser())
52+
{
53+
var client = new HttpClient() { BaseAddress = new Uri(Navigation.BaseUri) };
54+
var antiforgery = AntiforgeryState.GetAntiforgeryToken();
55+
if (antiforgery != null)
56+
{
57+
_posted = true;
58+
var request = new HttpRequestMessage(HttpMethod.Post, "api/antiforgery-form");
59+
var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[] { new ("Value", _value) });
60+
request.Content = content;
61+
request.Headers.Add("RequestVerificationToken", antiforgery.Value);
62+
var response = await client.SendAsync(request);
63+
if (response.IsSuccessStatusCode)
64+
{
65+
_succeeded = true;
66+
}
67+
else
68+
{
69+
_succeeded = false;
70+
}
71+
}
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)