dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.6k stars 10.06k forks source link

[Blazor][Antiforgery] Error 400 for a static SSR page, related to the constant antiforgery token update, if the redirect was from another domain. #58370

Open vsfeedback opened 1 month ago

vsfeedback commented 1 month ago

This issue has been moved from a ticket on Developer Community.


[severity:It bothers me. A fix would be nice] Problem: [Error 400][A valid antiforgery token was not provided with the request. Add an antiforgery token, or disable antiforgery validation for this endpoint] for a static SSR page, related to the constant antiforgery token update, if the redirect was from another domain.

I created a new default project BWA i-auto (i(nteractive)-server & i-wasm) + "include sample pages" in the latest VS 17.11.4 . I am attaching the project BlazorApp2.7z, it is a default + one simple test page (/test) with an edit form only.

If you open the /test page in a new tab and click submit - then the page works; Then if you open another /weather (sr-s-SSR=streaming rendering static SSR) page in a new tab from another domain - the previous page immediately gives 400 when submitting the form. (demonstrated in the video bandicam 2024-10-06 13-32-03-490.mp4) The problem is that the antiforgery token is updated if the redirect was to a new tab from another domain (demonstrated in the video bandicam 2024-10-06 13-48-18-096.mp4). The server IIS logs contain a new AF-token only for the last POST request to which the server responded with a 400 error. If you repeat this situation with opening the /weather page in a new tab with manual input of the url - then there is no such problem.

Is this a bug or some kind of Blazor technology issue for static pages? Can MS solve it?

The sample is temporarily deployed blazorapp220241005182634.azurewebsites.net

Possible workaround 1 for a s-SSR page: So far I see this workround: Use a js function that will check that the AF-token has not changed after loading the page and pressing the submit button. If it has changed, refresh the page (the AF-token will be updated automatically) or first ask the user to confirm that the page should be refreshed. But af-token cannot be read from cookies by design as "Httponly cookies' purpose is being inaccessible by script."

The question: How to find out with JavaScript that the AF-token has changed (a boolean sign is enough)?

Possible workaround 2 for a s-SSR page: When pages (like /weather) load, they detect that the token has changed and write the current timestamp to the browser's local storage. The test page, when a button is pressed, will detect that the state has changed (compare the timestamps via JS).

The question: How to find out with C# (OnInitializedAsync) that the AF-token has changed (a boolean sign is enough)?


Original Comments

Feedback Bot on 10/7/2024, 08:21 PM:

We have directed your feedback to the appropriate engineering team for further evaluation. The team will review the feedback and notify you about the next steps.

halter73 commented 1 month ago

If you add the following to your Program.cs, does that fix your issue?

builder.Services.AddAntiforgery(options =>
{
    options.Cookie.SameSite = SameSiteMode.None;
});

It appears that when the browser navigates to the /weather endpoint from another domain, antiforgery tries to reset the cookie because it wasn't sent during the initial request to the /weather endpoint. This invalidates the anti-csrf token used previously to render the /test form which was based on the previous cookie that got reset.

I don't think using SameSiteMode.None for the antiforgery cookie is a problem because an attacker trying to execute a cross-site request forgery attack should still be unable to generate a valid anti-csrf token. Maybe we should change the default.

However, I also see no reason Blazor should be triggering antiforgery logic at all when hitting the /weather endpoint. Not only is it inefficient, but it can also lead to problems like this. I know we're concerned about a form being rendered at a later point interactively, but maybe we could lazily make a delayed fetch request to get a valid cookie only once a form is rendered for components that support interactivity.

@blowdart @GrabYourPitchforks @javiercn Do any of you have opinions about using SameSiteMode.None for the antiforgery cookie? What about making that the default?

javiercn commented 1 month ago

However, I also see no reason Blazor should be triggering antiforgery logic at all when hitting the /weather endpoint. Not only is it inefficient, but it can also lead to problems like this. I know we're concerned about a form being rendered at a later point interactively, but maybe we could lazily make a delayed fetch request to get a valid cookie only once a form is rendered for components that support interactivity.

We have a bug for this, I'd rather fix this behavior than changing the cookie

vsvsav commented 1 month ago

If you add the following to your Program.cs, does that fix your issue?

builder.Services.AddAntiforgery(options => { options.Cookie.SameSite = SameSiteMode.None; });

Thank you! It works for this use case, but it breaks another: if you open from another domain "/test" page in a new tab, the test page returns a 400 when submitting the form. (Without these edits, this use case works)

,

mkArtakMSFT commented 1 month ago

Related to https://github.com/dotnet/aspnetcore/issues/51981