Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

correct way to implement Post/Redirect/Get (PRG) : AntiForgery tokens are not supposed to be cached, #7590

Closed
danielcongrove opened this issue Jul 12, 2018 · 10 comments
Labels
doc-bug Pri1 High priority, do before Pri2 and Pri3 Source - Docs.ms Docs Customer feedback via GitHub Issue
Milestone

Comments

@danielcongrove
Copy link

danielcongrove commented Jul 12, 2018

Hi!

After a form post fails server-side validation, I'm getting the following browser error message when I go to a different page and then use the browser's back button to return to original page with the form.

Error message in Firefox:

Document Expired

This document is no longer available.

The requested document is not available in Firefox’s cache.

As a security precaution, Firefox does not automatically re-request sensitive documents.
Click Try Again to re-request the document from the website.

Error message in Chrome:

Confirm Form Resubmission

This webpage requires data that you entered earlier in order to be properly displayed. You can send this data again, but by doing so you will repeat any action this page previously performed.

Press the reload button to resubmit the data needed to load the page.

ERR_CACHE_MISS

It appears to be caused by the Antiforgery system. When not using Antiforgery tokens, the browser error goes away.

Steps to reproduce:

Using the Razor pages tutorial project as an example (https://github.com/aspnet/Docs/tree/master/aspnetcore/tutorials/razor-pages/razor-pages-start/sample).

  1. Comment out the client side validation partial in /Pages/Movies/Create.cshtml file -- to force server-side validation.
  2. Add a new movie without the required fields filled -- so that server-side validation fails
  3. After the page is returned with the validation errors, click any link to go to a different page on the website
  4. Click the browser back button
  5. Browser error message is shown (Firefox & Chrome).

The problem seems to be caused by return Page(); in the Post action, when it's being used with an AntiForgery token in the form:

      public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Movie.Add(Movie);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }

I see this note in the Response Caching docs (https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-2.1),

Note

The Antiforgery system for generating secure tokens to prevent Cross-Site Request Forgery (CSRF) attacks sets the Cache-Control and Pragma headers to no-cache so that responses aren't cached. For information on how to disable antiforgery tokens for HTML form elements, see ASP.NET Core antiforgery configuration.

Assuming this is the problem, is there a solution to stop these browser error messages from occurring?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

@Rick-Anderson
Copy link
Contributor

@Tratcher please respond

@Rick-Anderson Rick-Anderson added Pri1 High priority, do before Pri2 and Pri3 Source - Docs.ms Docs Customer feedback via GitHub Issue labels Jul 14, 2018
@Rick-Anderson Rick-Anderson added this to the Backlog milestone Jul 14, 2018
@Tratcher
Copy link
Member

No, that's a browser feature. Navigating backwards works terribly for some HTTP features like form submission (POST request), so those messages are the browser being helpful and saying "You don't want to re-submit this form, it might cause nasty side-effects like double billing your credit card".

It is being triggered by the anti-forgery system because that is also a mechanic for protecting your form posts, though for some other scenarios.

Moral of the story: don't rely on the back button when there are POSTs involved, it's not safe. Provide ample navigation options on your page instead.

@Rick-Anderson
Copy link
Contributor

@Tratcher Is this the right doc to put that information (Navigating backwards works terribly for some HTTP features like form submission )?

@danielcongrove
Copy link
Author

danielcongrove commented Jul 14, 2018

@Tratcher Is there a proper way to use the new Antiforgery system and not get this browser error?

For example, in MVC5 there isn't this problem using the Antiforgery tokens. Or even the ASP.NET website's login form (https://login.asp.net/account/login), which appears to use Antiforgery tokens. If you try to login with an invalid password (which I assume triggers server-side validation), go to another page, and then click the browser back button to return to the login form, the page shows correctly -- no browser error and the Antiforgery token still intact.

But using ASP.NET Core 2.1, it seems any page that uses the Antiforgery system and returns View (or in the case of Razor pages, returns Page) from a POST action, results in the browser error back button issue.

            if (!ModelState.IsValid)
            {
                return Page();
            }

The browser error is more than a simple popup, asking if "you're sure you want to resubmit a form". This error message takes up the full browser window and makes it look like the web app is at fault to a user.

I think it may have to do with the Antiforgery tokens being set to no-cache, as the documentation points out. If this is the problem, is there a way to override this setting somehow? Or should we be returning something other than the View() or Page() when ModelState fails validation?

@Tratcher
Copy link
Member

@rynowak

@EntityAdam
Copy link

This is a not a language specific issue. This is a browser behavior. To say this was 'not an issue with ASP.NET MVC 5' is incorrect.

Solutions:
A common method of solving this is the PRG pattern which has been around since 1995.

An implementation of this for ASP.NET Core, using TempData can be found here

There is no easy way to implement PRG in ASP.NET Core, and I think this needs to be a quality of life improvement.

@danielcongrove
Copy link
Author

danielcongrove commented Aug 3, 2018

@EntityAdam Thanks for the response, but the issue isn't with Model State and the "Confirm Form Resubmission" popup. It's a different browser error, that appears to be caused by a change in how AntiForgery tokens are implemented in ASP.NET Core 2.1 (as opposed to ASP.NET MVC 5).

Your solution to change the return View(model) to RedirectToAction("Index") when ModelState fails validation may work to prevent the cache error from showing up, since it's doing a fresh GET and clearing out the entered form data. But as you pointed out, there is not an easy way to implement PRG to preserve the input values in ASP.NET Core. And I'm fine with having the "Confirm Form Resubmission" popup happen, but not with this new full browser window error message.

I encourage you to try the steps to reproduce in both MVC5 and ASP.NET Core 2.1. You'll notice it's a new browser error (separate from the "Confirm Form Resubmission" popup) that now occurs in Core 2.1, which was not present in MVC5.

@danielcongrove
Copy link
Author

danielcongrove commented Aug 8, 2018

Update: After receiving further feedback on StackOverflow about the issue (thanks Chris Pratt), I was told that the AntiForgery tokens are not supposed to be cached, and the browser error is what I should be seeing.

The PRG pattern posted by @EntityAdam is the correct solution. Sorry again for questioning your answer.

I would like to second @EntityAdam 's suggestion of adding some documentation on the correct way to implement PRG in ASP.NET Core.

Reference: https://stackoverflow.com/questions/51311726/asp-net-core-2-1-navigating-back-to-a-page-after-an-http-post-fails-validation/51661689?noredirect=1#comment90464976_51661689

@Rick-Anderson Rick-Anderson added Pri2 Priority 2 and removed Pri1 High priority, do before Pri2 and Pri3 labels Jan 14, 2019
@Rick-Anderson Rick-Anderson changed the title Navigating back to a page after an HTTP Post fails server-side validation, results in a browser error. correct way to implement PRG : AntiForgery tokens are not supposed to be cached, Jan 14, 2019
Copy link

asahire commented Jan 31, 2019

Hi Steve Smith, Fiyaz Hasan, and Rick Anderson,
I have used @Html.AntiForgeryToken() and [ValidateAntiforgeryToken] in my ASP.NET website.

I found one strange observation.
When I perform post request, it generates one anti-forgery token e.g. 'oZCEnRCusmwasddggfgtr1221dssdsdOm_yOf8XfL8k5GSQSMzKnHZuABnJSd9Q_VsdoW_dhberirHEIw7Gyib4XLVx641RYW01'.

Again, I perform one more post from the same form. It generates one more new token.

I am trying to perform CSRF attack by using old token i.e. 'oZCEnRCusmwasddggfgtr1221dssdsdOm_yOf8XfL8k5GSQSMzKnHZuABnJSd9Q_VsdoW_dhberirHEIw7Gyib4XLVx641RYW01'. It is still successfully posting the data on server. If I change the second last character from 0 to 1 i.e. 'oZCEnRCusmwasddggfgtr1221dssdsdOm_yOf8XfL8k5GSQSMzKnHZuABnJSd9Q_VsdoW_dhberirHEIw7Gyib4XLVx641RYW11'. It is still posting the data successfully.
I think, there is some issue in [ValidateAntiforgeryToken] mechanism.

@Rick-Anderson Rick-Anderson added Pri1 High priority, do before Pri2 and Pri3 and removed Pri2 Priority 2 labels Feb 6, 2019
@Rick-Anderson Rick-Anderson changed the title correct way to implement PRG : AntiForgery tokens are not supposed to be cached, correct way to implement Post/Redirect/Get (PRG) : AntiForgery tokens are not supposed to be cached, Nov 26, 2019
@Rick-Anderson
Copy link
Contributor

Thanks for contacting us.
We don’t have the resources to invest in this area, so we are closing the issue. Should your request generate enough 👍 responses, we’ll reconsider.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
doc-bug Pri1 High priority, do before Pri2 and Pri3 Source - Docs.ms Docs Customer feedback via GitHub Issue
Projects
None yet
Development

No branches or pull requests

6 participants