My team's exploration of ASP.NET Core and Razor Pages continues, and now we come to something that I was very used to doing in ASP.NET MVC, but hadn't done at all in Core: using anti-forgery tokens and validation.

A closeup of a hand holding a metal token or coin.
How many of these does it take to play Skee-ball? Photo by ZSun Fu / Unsplash

Lucky for us, ASP.NET Core has made using these tokens stupid easy. Let's explore how to use anti-forgery tokens in our ASP.NET Core Razor Pages apps!

Sample Project

As with most of my code-focused blog posts, this one has a sample project with examples over on GitHub. Check it out!

exceptionnotfound/AntiForgeryRazorPagesExamples
Contribute to exceptionnotfound/AntiForgeryRazorPagesExamples development by creating an account on GitHub.

What Are Anti-Forgery Tokens?

The purpose of using anti-forgery tokens is to prevent cross-site request forgery (CSRF) attacks. It does this by submitting two different values to the server on any given POST, both of which must exist in order for the server to allow the request.

One of those values is submitted as a cookie, and the other as form data. The cookie one is submitted automatically by browsers, but we as app developers must generate the one submitted in the form. ASP.NET Core provides us with several ways to do this.

A stack of chocolate-chip cookies, with a glass of milk in the background.
Wrong kind of cookie, but these are more delicious. Photo by Christina Branco / Unsplash

(For more reading, check out "Preventing Cross-Site Request Forgery (CSRF) Attacks in ASP.NET MVC Application" on the Microsoft docs site.)

How Do We Use Anti-Forgery Tokens in Razor Pages?

By default, new ASP.NET Core Razor Pages apps are already equipped with anti-forgery tokens and corresponding validation. On the page, the form tag helper will automatically render a hidden field containing an anti-forgery token.

Hence, this tag helper...

<form method="post">
    <button type="submit">Go</button>
</form>

...will generate the following HTML:

<form method="post">
    <button type="submit">Go</button>
    <input name="__RequestVerificationToken" type="hidden" value="aGeneratedAntiForgeryTokenValue">
</form>
Note that the anti-forgery token input is placed at the end of the form.

In this way, we don't need to worry about XSRF/CSRF attacks in our Razor Pages apps; we are already protected from them.

There are also a few ways to opt-out of using anti-forgery tokens in our Razor Pages apps.

Opting-Out of Anti-Forgery Token Validation

The first way to opt-out of using anti-forgery token validation is to do so globally by adding a convention to RazorPagesOptions in the Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages()
            .AddRazorPagesOptions(options =>
            {
                options.Conventions
                       .ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
            });
}

This inserts an IgnoreAntiforgeryTokenAttribute into our request pipeline, which skips the validation of the token for every request (note that the token will still be generated in our forms, just not validated on the server side).

You can also opt-out on individual pages, by using the same attribute:

[IgnoreAntiforgeryToken(Order = 1001)]
public class InvalidModel : PageModel
{
    public void OnGet() { }
}
The Order property needs to be > 1000 in order to skip the normal anti-forgery token validation.

If you first disable the anti-forgery validation site-wide, you can re-enable it on certain pages by using [ValidateAntiForgeryToken]:

[ValidateAntiForgeryToken]
public class ValidateModel : PageModel
{
    public void OnGet() { }
    public void OnPost() { }
}

If you would also like the form to not render the hidden anti-forgery token field, you can disable that in the form tag helper:

<form method="post" asp-antiforgery="false"></form>

In this way, we can have fine-grained control over which pages use anti-forgery validation, and which do not.

Anti-Forgery Validation and AJAX Requests

If anti-forgery token validation is enabled (either for a single page or for the entire site) then requests made via AJAX must include the token in their request.

For example, you could include the token as a header object in an AJAX request:

$.ajax({
    type: "POST",
    url: 'yourURL',
    headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() }
})

If you want to merely generate an anti-forgery token somewhere on the page, you can either generate a new form with the method set to POST...

<form method="post"></form>

...or use the HTML Helper...

@Html.AntiForgeryToken()

..and then use jQuery or similar to select the value generated by these methods, and include it in your AJAX request.

But what if you don't want to have unnecessary elements on your page?  In this case, you can inject IAntiforgery to the page and use the method GetAndStoreTokens() to generate the token.

@page
@model AntiForgeryRazorPagesExamples.AjaxModel
@inject IAntiforgery antiforgery
@{
    var token = antiforgery.GetAndStoreTokens(HttpContext).RequestToken;
}

...

@section Scripts
{
<script>
    $(function () {
        $.post("/search?handler=ajax", { __RequestVerificationToken: '@token' });
    })
</script>
}

Summary

ASP.NET Core Razor Pages really, really wants you to use anti-forgery tokens. Remember the following:

  • Anti-forgery token validation is enabled by default in Razor Pages.
  • You can disable validation either globally or on individual pages by using [IgnoreAntiforgeryToken].
  • You can prevent forms from creating anti-forgery tokens by using asp-antiforgery="false" in the form tag helper.
  • When validation is enabled, you need to include anti-forgery tokens in AJAX requests, and can do so in several ways.

If you are disabling anti-forgery tokens at all (either app-wide or per-page) I'd be interested in hearing why you did so!

Don't forget to check out the sample project over on GitHub!

Happy Coding!