Training
Module
Secure a .NET web app with the ASP.NET Core Identity framework - Training
Learn how to add authentication and authorization to a .NET web app using the ASP.NET Core Identity framework.
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
By Fiyaz Hasan and Rick Anderson
Cross-site request forgery is an attack against web-hosted apps whereby a malicious web app can influence the interaction between a client browser and a web app that trusts that browser. These attacks are possible because web browsers send some types of authentication tokens automatically with every request to a website. This form of exploit is also known as a one-click attack or session riding because the attack takes advantage of the user's previously authenticated session. Cross-site request forgery is also known as XSRF or CSRF.
An example of a CSRF attack:
A user signs into www.good-banking-site.example.com
using forms authentication. The server authenticates the user and issues a response that includes an authentication cookie. The site is vulnerable to attack because it trusts any request that it receives with a valid authentication cookie.
The user visits a malicious site, www.bad-crook-site.example.com
.
The malicious site, www.bad-crook-site.example.com
, contains an HTML form similar to the following example:
<h1>Congratulations! You're a Winner!</h1>
<form action="https://good-banking-site.com/api/account" method="post">
<input type="hidden" name="Transaction" value="withdraw" />
<input type="hidden" name="Amount" value="1000000" />
<input type="submit" value="Click to collect your prize!" />
</form>
Notice that the form's action
posts to the vulnerable site, not to the malicious site. This is the "cross-site" part of CSRF.
The user selects the submit button. The browser makes the request and automatically includes the authentication cookie for the requested domain, www.good-banking-site.example.com
.
The request runs on the www.good-banking-site.example.com
server with the user's authentication context and can perform any action that an authenticated user is allowed to perform.
In addition to the scenario where the user selects the button to submit the form, the malicious site could:
These alternative scenarios don't require any action or input from the user other than initially visiting the malicious site.
Using HTTPS doesn't prevent a CSRF attack. The malicious site can send an https://www.good-banking-site.com/
request as easily as it can send an insecure request.
Some attacks target endpoints that respond to GET requests, in which case an image tag can be used to perform the action. This form of attack is common on forum sites that permit images but block JavaScript. Apps that change state on GET requests, where variables or resources are altered, are vulnerable to malicious attacks. GET requests that change state are insecure. A best practice is to never change state on a GET request.
CSRF attacks are possible against web apps that use cookies for authentication because:
However, CSRF attacks aren't limited to exploiting cookies. For example, Basic and Digest authentication are also vulnerable. After a user signs in with Basic or Digest authentication, the browser automatically sends the credentials until the session ends.
In this context, session refers to the client-side session during which the user is authenticated. It's unrelated to server-side sessions or ASP.NET Core Session Middleware.
Users can protect against CSRF vulnerabilities by taking precautions:
However, CSRF vulnerabilities are fundamentally a problem with the web app, not the end user.
Cookie-based authentication is a popular form of authentication. Token-based authentication systems are growing in popularity, especially for Single Page Applications (SPAs).
When a user authenticates using their username and password they're issued a token containing an authentication ticket. The token can be used for authentication and authorization. The token is stored as a cookie that's sent with every request the client makes. Generating and validating this cookie is performed with the Cookie Authentication Middleware. The middleware serializes a user principal into an encrypted cookie. On subsequent requests, the middleware validates the cookie, recreates the principal, and assigns the principal to the HttpContext.User property.
When a user is authenticated, they're issued a token (not an antiforgery token). The token contains user information in the form of claims or a reference token that points the app to user state maintained in the app. When a user attempts to access a resource that requires authentication, the token is sent to the app with an extra authorization header in the form of a Bearer token. This approach makes the app stateless. In each subsequent request, the token is passed in the request for server-side validation. This token isn't encrypted; it's encoded. On the server, the token is decoded to access its information. To send the token on subsequent requests, store the token in the browser's local storage. Placing a token in the browser local storage and retrieving it and using it as a bearer token provides protection against CSRF attacks. However, should the app be vulnerable to script injection via XSS or a compromised external JavaScript file, a cyberattacker could retrieve any value from local storage and send it to themselves. ASP.NET Core encodes all server side output from variables by default, reducing the risk of XSS. If you override this behavior by using Html.Raw or custom code with untrusted input then you may increase the risk of XSS.
Don't be concerned about CSRF vulnerability if the token is stored in the browser's local storage. CSRF is a concern when the token is stored in a cookie. For more information, see the GitHub issue SPA code sample adds two cookies.
Shared hosting environments are vulnerable to session hijacking, sign-in CSRF, and other attacks.
Although example1.contoso.net
and example2.contoso.net
are different hosts, there's an implicit trust relationship between hosts under the *.contoso.net
domain. This implicit trust relationship allows potentially untrusted hosts to affect each other's cookies (the same-origin policies that govern AJAX requests don't necessarily apply to HTTP cookies).
Attacks that exploit trusted cookies between apps hosted on the same domain can be prevented by not sharing domains. When each app is hosted on its own domain, there's no implicit cookie trust relationship to exploit.
Warning
ASP.NET Core implements antiforgery using ASP.NET Core Data Protection. The data protection stack must be configured to work in a server farm. For more information, see Configuring data protection.
Antiforgery middleware is added to the Dependency injection container when one of the following APIs is called in Program.cs
:
For more information, see Antiforgery with Minimal APIs.
The FormTagHelper injects antiforgery tokens into HTML form elements. The following markup in a Razor file automatically generates antiforgery tokens:
<form method="post">
<!-- ... -->
</form>
Similarly, IHtmlHelper.BeginForm generates antiforgery tokens by default if the form's method isn't GET.
The automatic generation of antiforgery tokens for HTML form elements happens when the <form>
tag contains the method="post"
attribute and either of the following are true:
action=""
).<form method="post">
).Automatic generation of antiforgery tokens for HTML form elements can be disabled:
Explicitly disable antiforgery tokens with the asp-antiforgery
attribute:
<form method="post" asp-antiforgery="false">
<!-- ... -->
</form>
The form element is opted-out of Tag Helpers by using the Tag Helper ! opt-out symbol:
<!form method="post">
<!-- ... -->
</!form>
Remove the FormTagHelper
from the view. The FormTagHelper
can be removed from a view by adding the following directive to the Razor view:
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages are automatically protected from XSRF/CSRF. For more information, see XSRF/CSRF and Razor Pages.
The most common approach to protecting against CSRF attacks is to use the Synchronizer Token Pattern (STP). STP is used when the user requests a page with form data:
The token is unique and unpredictable. The token can also be used to ensure proper sequencing of a series of requests (for example, ensuring the request sequence of: page 1 > page 2 > page 3). All of the forms in ASP.NET Core MVC and Razor Pages templates generate antiforgery tokens. The following pair of view examples generates antiforgery tokens:
<form asp-action="Index" asp-controller="Home" method="post">
<!-- ... -->
</form>
@using (Html.BeginForm("Index", "Home"))
{
<!-- ... -->
}
Explicitly add an antiforgery token to a <form>
element without using Tag Helpers with the HTML helper @Html.AntiForgeryToken
:
<form asp-action="Index" asp-controller="Home" method="post">
@Html.AntiForgeryToken()
<!-- ... -->
</form>
In each of the preceding cases, ASP.NET Core adds a hidden form field similar to the following example:
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core includes three filters for working with antiforgery tokens:
Calling AddControllers does not enable antiforgery tokens. AddControllersWithViews must be called to have built-in antiforgery token support.
With the Synchronizer Token Pattern, only the most recently loaded page contains a valid antiforgery token. Using multiple tabs can be problematic. For example, if a user opens multiple tabs:
Antiforgery token validation failed. The antiforgery cookie token and request token do not match
Consider alternative CSRF protection patterns if this poses an issue.
Customize AntiforgeryOptions in Program.cs
:
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
Set the antiforgery Cookie
properties using the properties of the CookieBuilder class, as shown in the following table.
Option | Description |
---|---|
Cookie | Determines the settings used to create the antiforgery cookies. |
FormFieldName | The name of the hidden form field used by the antiforgery system to render antiforgery tokens in views. |
HeaderName | The name of the header used by the antiforgery system. If null , the system considers only form data. |
SuppressXFrameOptionsHeader | Specifies whether to suppress generation of the X-Frame-Options header. By default, the header is generated with a value of "SAMEORIGIN". Defaults to false . |
For more information, see CookieAuthenticationOptions.
IAntiforgery provides the API to configure antiforgery features. IAntiforgery
can be requested in Program.cs
using WebApplication.Services. The following example uses middleware from the app's home page to generate an antiforgery token and send it in the response as a cookie:
app.UseRouting();
app.UseAuthorization();
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
The preceding example sets a cookie named XSRF-TOKEN
. The client can read this cookie and provide its value as a header attached to AJAX requests. For example, Angular includes built-in XSRF protection that reads a cookie named XSRF-TOKEN
by default.
The ValidateAntiForgeryToken action filter can be applied to an individual action, a controller, or globally. Requests made to actions that have this filter applied are blocked unless the request includes a valid antiforgery token:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
The ValidateAntiForgeryToken
attribute requires a token for requests to the action methods it marks, including HTTP GET requests. If the ValidateAntiForgeryToken
attribute is applied across the app's controllers, it can be overridden with the IgnoreAntiforgeryToken
attribute.
Instead of broadly applying the ValidateAntiForgeryToken
attribute and then overriding it with IgnoreAntiforgeryToken
attributes, the AutoValidateAntiforgeryToken attribute can be used. This attribute works identically to the ValidateAntiForgeryToken
attribute, except that it doesn't require tokens for requests made using the following HTTP methods:
We recommend use of AutoValidateAntiforgeryToken
broadly for non-API scenarios. This attribute ensures POST actions are protected by default. The alternative is to ignore antiforgery tokens by default, unless ValidateAntiForgeryToken
is applied to individual action methods. It's more likely in this scenario for a POST action method to be left unprotected by mistake, leaving the app vulnerable to CSRF attacks. All POSTs should send the antiforgery token.
APIs don't have an automatic mechanism for sending the non-cookie part of the token. The implementation probably depends on the client code implementation. Some examples are shown below:
Class-level example:
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
Global example:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
The IgnoreAntiforgeryToken filter is used to eliminate the need for an antiforgery token for a given action (or controller). When applied, this filter overrides ValidateAntiForgeryToken
and AutoValidateAntiforgeryToken
filters specified at a higher level (globally or on a controller).
[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
// ...
return RedirectToAction();
}
Tokens should be refreshed after the user is authenticated by redirecting the user to a view or Razor Pages page.
In traditional HTML-based apps, antiforgery tokens are passed to the server using hidden form fields. In modern JavaScript-based apps and SPAs, many requests are made programmatically. These AJAX requests may use other techniques, such as request headers or cookies, to send the token.
If cookies are used to store authentication tokens and to authenticate API requests on the server, CSRF is a potential problem. If local storage is used to store the token, CSRF vulnerability might be mitigated because values from local storage aren't sent automatically to the server with every request. Using local storage to store the antiforgery token on the client and sending the token as a request header is a recommended approach.
For more information, see ASP.NET Core Blazor authentication and authorization.
Using JavaScript with views, the token can be created using a service from within the view. Inject the IAntiforgery service into the view and call GetAndStoreTokens:
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@{
ViewData["Title"] = "JavaScript";
var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}
<input id="RequestVerificationToken" type="hidden" value="@requestToken" />
<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>
@section Scripts {
<script>
document.addEventListener("DOMContentLoaded", () => {
const resultElement = document.getElementById("result");
document.getElementById("button").addEventListener("click", async () => {
const response = await fetch("@Url.Action("FetchEndpoint")", {
method: "POST",
headers: {
RequestVerificationToken:
document.getElementById("RequestVerificationToken").value
}
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
});
});
</script>
}
The preceding example uses JavaScript to read the hidden field value for the AJAX POST header.
This approach eliminates the need to deal directly with setting cookies from the server or reading them from the client. However, when injecting the IAntiforgery service isn't possible, use JavaScript to access tokens in cookies:
same-origin
.Assuming the script sends the token in a request header called X-XSRF-TOKEN
, configure the antiforgery service to look for the X-XSRF-TOKEN
header:
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
The following example adds a protected endpoint that writes the request token to a JavaScript-readable cookie:
app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
var tokens = forgeryService.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
return Results.Ok();
}).RequireAuthorization();
The following example uses JavaScript to make an AJAX request to obtain the token and make another request with the appropriate header:
var response = await fetch("/antiforgery/token", {
method: "GET",
headers: { "Authorization": authorizationToken }
});
if (response.ok) {
// https://developer.mozilla.org/docs/web/api/document/cookie
const xsrfToken = document.cookie
.split("; ")
.find(row => row.startsWith("XSRF-TOKEN="))
.split("=")[1];
response = await fetch("/JavaScript/FetchEndpoint", {
method: "POST",
headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
Note
When the antiforgery token is provided in both the request header and in the form payload, only the token in the header is validated.
Call AddAntiforgery and UseAntiforgery(IApplicationBuilder) to register antiforgery services in DI. Antiforgery tokens are used to mitigate cross-site request forgery attacks.
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
app.MapGet("/", () => "Hello World!");
app.Run();
The antiforgery middleware:
The antiforgery token is only validated if:
RequiresValidation=true
.Note: When enabled manually, the antiforgery middleware must run after the authentication and authorization middleware to prevent reading form data when the user is unauthenticated.
By default, minimal APIs that accept form data require antiforgery token validation.
Consider the following GenerateForm
method:
public static string GenerateForm(string action,
AntiforgeryTokenSet token, bool UseToken=true)
{
string tokenInput = "";
if (UseToken)
{
tokenInput = $@"<input name=""{token.FormFieldName}""
type=""hidden"" value=""{token.RequestToken}"" />";
}
return $@"
<html><body>
<form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
{tokenInput}
<input type=""text"" name=""name"" />
<input type=""date"" name=""dueDate"" />
<input type=""checkbox"" name=""isCompleted"" />
<input type=""submit"" />
</form>
</body></html>
";
}
The preceding code has three arguments, the action, the antiforgery token, and a bool
indicating whether the token should be used.
Consider the following sample:
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
// Pass token
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
return Results.Content(MyHtml.GenerateForm("/todo", token), "text/html");
});
// Don't pass a token, fails
app.MapGet("/SkipToken", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
return Results.Content(MyHtml.GenerateForm("/todo",token, false ), "text/html");
});
// Post to /todo2. DisableAntiforgery on that endpoint so no token needed.
app.MapGet("/DisableAntiforgery", (HttpContext context, IAntiforgery antiforgery) =>
{
var token = antiforgery.GetAndStoreTokens(context);
return Results.Content(MyHtml.GenerateForm("/todo2", token, false), "text/html");
});
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
.DisableAntiforgery();
app.Run();
class Todo
{
public required string Name { get; set; }
public bool IsCompleted { get; set; }
public DateTime DueDate { get; set; }
}
public static class MyHtml
{
public static string GenerateForm(string action,
AntiforgeryTokenSet token, bool UseToken=true)
{
string tokenInput = "";
if (UseToken)
{
tokenInput = $@"<input name=""{token.FormFieldName}""
type=""hidden"" value=""{token.RequestToken}"" />";
}
return $@"
<html><body>
<form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
{tokenInput}
<input type=""text"" name=""name"" />
<input type=""date"" name=""dueDate"" />
<input type=""checkbox"" name=""isCompleted"" />
<input type=""submit"" />
</form>
</body></html>
";
}
}
In the preceding code, posts to:
/todo
require a valid antiforgery token./todo2
do not require a valid antiforgery token because DisableAntiforgery
is called.app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
.DisableAntiforgery();
A POST to:
/todo
from the form generated by the /
endpoint succeeds because the antiforgery token is valid./todo
from the form generated by the /SkipToken
fails because the antiforgery is not included./todo2
from the form generated by the /DisableAntiforgery
endpoint succeeds because the antiforgery is not required.app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
.DisableAntiforgery();
When a form is submitted without a valid antiforgery token:
When using Windows Authentication, application endpoints must be protected against CSRF attacks in the same way as done for cookies. The browser implicitly sends the authentication context to the server and endpoints need to be protected against CSRF attacks.
The IAntiforgeryAdditionalDataProvider type allows developers to extend the behavior of the anti-CSRF system by round-tripping additional data in each token. The GetAdditionalData method is called each time a field token is generated, and the return value is embedded within the generated token. An implementer could return a timestamp, a nonce, or any other value and then call ValidateAdditionalData to validate this data when the token is validated. The client's username is already embedded in the generated tokens, so there's no need to include this information. If a token includes supplemental data but no IAntiForgeryAdditionalDataProvider
is configured, the supplemental data isn't validated.
Cross-site request forgery (also known as XSRF or CSRF) is an attack against web-hosted apps whereby a malicious web app can influence the interaction between a client browser and a web app that trusts that browser. These attacks are possible because web browsers send some types of authentication tokens automatically with every request to a website. This form of exploit is also known as a one-click attack or session riding because the attack takes advantage of the user's previously authenticated session.
An example of a CSRF attack:
A user signs into www.good-banking-site.example.com
using forms authentication. The server authenticates the user and issues a response that includes an authentication cookie. The site is vulnerable to attack because it trusts any request that it receives with a valid authentication cookie.
The user visits a malicious site, www.bad-crook-site.example.com
.
The malicious site, www.bad-crook-site.example.com
, contains an HTML form similar to the following example:
<h1>Congratulations! You're a Winner!</h1>
<form action="https://good-banking-site.com/api/account" method="post">
<input type="hidden" name="Transaction" value="withdraw" />
<input type="hidden" name="Amount" value="1000000" />
<input type="submit" value="Click to collect your prize!" />
</form>
Notice that the form's action
posts to the vulnerable site, not to the malicious site. This is the "cross-site" part of CSRF.
The user selects the submit button. The browser makes the request and automatically includes the authentication cookie for the requested domain, www.good-banking-site.example.com
.
The request runs on the www.good-banking-site.example.com
server with the user's authentication context and can perform any action that an authenticated user is allowed to perform.
In addition to the scenario where the user selects the button to submit the form, the malicious site could:
These alternative scenarios don't require any action or input from the user other than initially visiting the malicious site.
Using HTTPS doesn't prevent a CSRF attack. The malicious site can send an https://www.good-banking-site.com/
request just as easily as it can send an insecure request.
Some attacks target endpoints that respond to GET requests, in which case an image tag can be used to perform the action. This form of attack is common on forum sites that permit images but block JavaScript. Apps that change state on GET requests, where variables or resources are altered, are vulnerable to malicious attacks. GET requests that change state are insecure. A best practice is to never change state on a GET request.
CSRF attacks are possible against web apps that use cookies for authentication because:
However, CSRF attacks aren't limited to exploiting cookies. For example, Basic and Digest authentication are also vulnerable. After a user signs in with Basic or Digest authentication, the browser automatically sends the credentials until the session ends.
In this context, session refers to the client-side session during which the user is authenticated. It's unrelated to server-side sessions or ASP.NET Core Session Middleware.
Users can protect against CSRF vulnerabilities by taking precautions:
However, CSRF vulnerabilities are fundamentally a problem with the web app, not the end user.
Cookie-based authentication is a popular form of authentication. Token-based authentication systems are growing in popularity, especially for Single Page Applications (SPAs).
When a user authenticates using their username and password, they're issued a token, containing an authentication ticket that can be used for authentication and authorization. The token is stored as a cookie that's sent with every request the client makes. Generating and validating this cookie is performed by the Cookie Authentication Middleware. The middleware serializes a user principal into an encrypted cookie. On subsequent requests, the middleware validates the cookie, recreates the principal, and assigns the principal to the HttpContext.User property.
When a user is authenticated, they're issued a token (not an antiforgery token). The token contains user information in the form of claims or a reference token that points the app to user state maintained in the app. When a user attempts to access a resource that requires authentication, the token is sent to the app with an extra authorization header in the form of a Bearer token. This approach makes the app stateless. In each subsequent request, the token is passed in the request for server-side validation. This token isn't encrypted; it's encoded. On the server, the token is decoded to access its information. To send the token on subsequent requests, store the token in the browser's local storage. Placing a token in the browser local storage and retrieving it and using it as a bearer token provides protection against CSRF attacks. However, should the app be vulnerable to script injection via XSS or a compromised external javascript file, a cyberattacker could retrieve any value from local storage and send it to themselves. ASP.NET Core encodes all server side output from variables by default, reducing the risk of XSS. If you override this behavior by using Html.Raw or custom code with untrusted input then you may increase the risk of XSS.
Don't be concerned about CSRF vulnerability if the token is stored in the browser's local storage. CSRF is a concern when the token is stored in a cookie. For more information, see the GitHub issue SPA code sample adds two cookies.
Shared hosting environments are vulnerable to session hijacking, login CSRF, and other attacks.
Although example1.contoso.net
and example2.contoso.net
are different hosts, there's an implicit trust relationship between hosts under the *.contoso.net
domain. This implicit trust relationship allows potentially untrusted hosts to affect each other's cookies (the same-origin policies that govern AJAX requests don't necessarily apply to HTTP cookies).
Attacks that exploit trusted cookies between apps hosted on the same domain can be prevented by not sharing domains. When each app is hosted on its own domain, there's no implicit cookie trust relationship to exploit.
Warning
ASP.NET Core implements antiforgery using ASP.NET Core Data Protection. The data protection stack must be configured to work in a server farm. For more information, see Configuring data protection.
Antiforgery middleware is added to the Dependency injection container when one of the following APIs is called in Program.cs
:
The FormTagHelper injects antiforgery tokens into HTML form elements. The following markup in a Razor file automatically generates antiforgery tokens:
<form method="post">
<!-- ... -->
</form>
Similarly, IHtmlHelper.BeginForm generates antiforgery tokens by default if the form's method isn't GET.
The automatic generation of antiforgery tokens for HTML form elements happens when the <form>
tag contains the method="post"
attribute and either of the following are true:
action=""
).<form method="post">
).Automatic generation of antiforgery tokens for HTML form elements can be disabled:
Explicitly disable antiforgery tokens with the asp-antiforgery
attribute:
<form method="post" asp-antiforgery="false">
<!-- ... -->
</form>
The form element is opted-out of Tag Helpers by using the Tag Helper ! opt-out symbol:
<!form method="post">
<!-- ... -->
</!form>
Remove the FormTagHelper
from the view. The FormTagHelper
can be removed from a view by adding the following directive to the Razor view:
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages are automatically protected from XSRF/CSRF. For more information, see XSRF/CSRF and Razor Pages.
The most common approach to defending against CSRF attacks is to use the Synchronizer Token Pattern (STP). STP is used when the user requests a page with form data:
The token is unique and unpredictable. The token can also be used to ensure proper sequencing of a series of requests (for example, ensuring the request sequence of: page 1 > page 2 > page 3). All of the forms in ASP.NET Core MVC and Razor Pages templates generate antiforgery tokens. The following pair of view examples generates antiforgery tokens:
<form asp-action="Index" asp-controller="Home" method="post">
<!-- ... -->
</form>
@using (Html.BeginForm("Index", "Home"))
{
<!-- ... -->
}
Explicitly add an antiforgery token to a <form>
element without using Tag Helpers with the HTML helper @Html.AntiForgeryToken
:
<form asp-action="Index" asp-controller="Home" method="post">
@Html.AntiForgeryToken()
<!-- ... -->
</form>
In each of the preceding cases, ASP.NET Core adds a hidden form field similar to the following example:
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core includes three filters for working with antiforgery tokens:
Calling AddControllers does not enable antiforgery tokens. AddControllersWithViews must be called to have built-in antiforgery token support.
With the Synchronizer Token Pattern, only the most recently loaded page contains a valid antiforgery token. Using multiple tabs can be problematic. For example, if a user opens multiple tabs:
Antiforgery token validation failed. The antiforgery cookie token and request token do not match
Consider alternative CSRF protection patterns if this poses an issue.
Customize AntiforgeryOptions in Program.cs
:
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
Set the antiforgery Cookie
properties using the properties of the CookieBuilder class, as shown in the following table.
Option | Description |
---|---|
Cookie | Determines the settings used to create the antiforgery cookies. |
FormFieldName | The name of the hidden form field used by the antiforgery system to render antiforgery tokens in views. |
HeaderName | The name of the header used by the antiforgery system. If null , the system considers only form data. |
SuppressXFrameOptionsHeader | Specifies whether to suppress generation of the X-Frame-Options header. By default, the header is generated with a value of "SAMEORIGIN". Defaults to false . |
For more information, see CookieAuthenticationOptions.
IAntiforgery provides the API to configure antiforgery features. IAntiforgery
can be requested in Program.cs
using WebApplication.Services. The following example uses middleware from the app's home page to generate an antiforgery token and send it in the response as a cookie:
app.UseRouting();
app.UseAuthorization();
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
The preceding example sets a cookie named XSRF-TOKEN
. The client can read this cookie and provide its value as a header attached to AJAX requests. For example, Angular includes built-in XSRF protection that reads a cookie named XSRF-TOKEN
by default.
The ValidateAntiForgeryToken action filter can be applied to an individual action, a controller, or globally. Requests made to actions that have this filter applied are blocked unless the request includes a valid antiforgery token:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
The ValidateAntiForgeryToken
attribute requires a token for requests to the action methods it marks, including HTTP GET requests. If the ValidateAntiForgeryToken
attribute is applied across the app's controllers, it can be overridden with the IgnoreAntiforgeryToken
attribute.
Instead of broadly applying the ValidateAntiForgeryToken
attribute and then overriding it with IgnoreAntiforgeryToken
attributes, the AutoValidateAntiforgeryToken attribute can be used. This attribute works identically to the ValidateAntiForgeryToken
attribute, except that it doesn't require tokens for requests made using the following HTTP methods:
We recommend use of AutoValidateAntiforgeryToken
broadly for non-API scenarios. This attribute ensures POST actions are protected by default. The alternative is to ignore antiforgery tokens by default, unless ValidateAntiForgeryToken
is applied to individual action methods. It's more likely in this scenario for a POST action method to be left unprotected by mistake, leaving the app vulnerable to CSRF attacks. All POSTs should send the antiforgery token.
APIs don't have an automatic mechanism for sending the non-cookie part of the token. The implementation probably depends on the client code implementation. Some examples are shown below:
Class-level example:
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
Global example:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
The IgnoreAntiforgeryToken filter is used to eliminate the need for an antiforgery token for a given action (or controller). When applied, this filter overrides ValidateAntiForgeryToken
and AutoValidateAntiforgeryToken
filters specified at a higher level (globally or on a controller).
[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
// ...
return RedirectToAction();
}
Tokens should be refreshed after the user is authenticated by redirecting the user to a view or Razor Pages page.
In traditional HTML-based apps, antiforgery tokens are passed to the server using hidden form fields. In modern JavaScript-based apps and SPAs, many requests are made programmatically. These AJAX requests may use other techniques (such as request headers or cookies) to send the token.
If cookies are used to store authentication tokens and to authenticate API requests on the server, CSRF is a potential problem. If local storage is used to store the token, CSRF vulnerability might be mitigated because values from local storage aren't sent automatically to the server with every request. Using local storage to store the antiforgery token on the client and sending the token as a request header is a recommended approach.
Using JavaScript with views, the token can be created using a service from within the view. Inject the IAntiforgery service into the view and call GetAndStoreTokens:
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@{
ViewData["Title"] = "JavaScript";
var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}
<input id="RequestVerificationToken" type="hidden" value="@requestToken" />
<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>
@section Scripts {
<script>
document.addEventListener("DOMContentLoaded", () => {
const resultElement = document.getElementById("result");
document.getElementById("button").addEventListener("click", async () => {
const response = await fetch("@Url.Action("FetchEndpoint")", {
method: "POST",
headers: {
RequestVerificationToken:
document.getElementById("RequestVerificationToken").value
}
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
});
});
</script>
}
The preceding example uses JavaScript to read the hidden field value for the AJAX POST header.
This approach eliminates the need to deal directly with setting cookies from the server or reading them from the client. However, when injecting the IAntiforgery service isn't possible, use JavaScript to access tokens in cookies:
same-origin
.Assuming the script sends the token in a request header called X-XSRF-TOKEN
, configure the antiforgery service to look for the X-XSRF-TOKEN
header:
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
The following example adds a protected endpoint that writes the request token to a JavaScript-readable cookie:
app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
var tokens = forgeryService.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
return Results.Ok();
}).RequireAuthorization();
The following example uses JavaScript to make an AJAX request to obtain the token and make another request with the appropriate header:
var response = await fetch("/antiforgery/token", {
method: "GET",
headers: { "Authorization": authorizationToken }
});
if (response.ok) {
// https://developer.mozilla.org/docs/web/api/document/cookie
const xsrfToken = document.cookie
.split("; ")
.find(row => row.startsWith("XSRF-TOKEN="))
.split("=")[1];
response = await fetch("/JavaScript/FetchEndpoint", {
method: "POST",
headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
Note
When the antiforgery token is provided in both the request header and in the form payload, only the token in the header is validated.
Minimal APIs
do not support the usage of the included filters (ValidateAntiForgeryToken
, AutoValidateAntiforgeryToken
, IgnoreAntiforgeryToken
), however, IAntiforgery provides the required APIs to validate a request.
The following example creates a filter that validates the antiforgery token:
internal static class AntiForgeryExtensions
{
public static TBuilder ValidateAntiforgery<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
{
return builder.AddEndpointFilter(routeHandlerFilter: async (context, next) =>
{
try
{
var antiForgeryService = context.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
await antiForgeryService.ValidateRequestAsync(context.HttpContext);
}
catch (AntiforgeryValidationException)
{
return Results.BadRequest("Antiforgery token validation failed.");
}
return await next(context);
});
}
}
The filter can then be applied to an endpoint:
app.MapPost("api/upload", (IFormFile name) => Results.Accepted())
.RequireAuthorization()
.ValidateAntiforgery();
When using Windows Authentication, application endpoints must be protected against CSRF attacks in the same way as done for cookies. The browser implicitly sends the authentication context to the server and endpoints need to be protected against CSRF attacks.
The IAntiforgeryAdditionalDataProvider type allows developers to extend the behavior of the anti-CSRF system by round-tripping additional data in each token. The GetAdditionalData method is called each time a field token is generated, and the return value is embedded within the generated token. An implementer could return a timestamp, a nonce, or any other value and then call ValidateAdditionalData to validate this data when the token is validated. The client's username is already embedded in the generated tokens, so there's no need to include this information. If a token includes supplemental data but no IAntiForgeryAdditionalDataProvider
is configured, the supplemental data isn't validated.
Cross-site request forgery (also known as XSRF or CSRF) is an attack against web-hosted apps whereby a malicious web app can influence the interaction between a client browser and a web app that trusts that browser. These attacks are possible because web browsers send some types of authentication tokens automatically with every request to a website. This form of exploit is also known as a one-click attack or session riding because the attack takes advantage of the user's previously authenticated session.
An example of a CSRF attack:
A user signs into www.good-banking-site.example.com
using forms authentication. The server authenticates the user and issues a response that includes an authentication cookie. The site is vulnerable to attack because it trusts any request that it receives with a valid authentication cookie.
The user visits a malicious site, www.bad-crook-site.example.com
.
The malicious site, www.bad-crook-site.example.com
, contains an HTML form similar to the following example:
<h1>Congratulations! You're a Winner!</h1>
<form action="https://good-banking-site.com/api/account" method="post">
<input type="hidden" name="Transaction" value="withdraw" />
<input type="hidden" name="Amount" value="1000000" />
<input type="submit" value="Click to collect your prize!" />
</form>
Notice that the form's action
posts to the vulnerable site, not to the malicious site. This is the "cross-site" part of CSRF.
The user selects the submit button. The browser makes the request and automatically includes the authentication cookie for the requested domain, www.good-banking-site.example.com
.
The request runs on the www.good-banking-site.example.com
server with the user's authentication context and can perform any action that an authenticated user is allowed to perform.
In addition to the scenario where the user selects the button to submit the form, the malicious site could:
These alternative scenarios don't require any action or input from the user other than initially visiting the malicious site.
Using HTTPS doesn't prevent a CSRF attack. The malicious site can send an https://www.good-banking-site.com/
request just as easily as it can send an insecure request.
Some attacks target endpoints that respond to GET requests, in which case an image tag can be used to perform the action. This form of attack is common on forum sites that permit images but block JavaScript. Apps that change state on GET requests, where variables or resources are altered, are vulnerable to malicious attacks. GET requests that change state are insecure. A best practice is to never change state on a GET request.
CSRF attacks are possible against web apps that use cookies for authentication because:
However, CSRF attacks aren't limited to exploiting cookies. For example, Basic and Digest authentication are also vulnerable. After a user signs in with Basic or Digest authentication, the browser automatically sends the credentials until the session ends.
In this context, session refers to the client-side session during which the user is authenticated. It's unrelated to server-side sessions or ASP.NET Core Session Middleware.
Users can protect against CSRF vulnerabilities by taking precautions:
However, CSRF vulnerabilities are fundamentally a problem with the web app, not the end user.
Cookie-based authentication is a popular form of authentication. Token-based authentication systems are growing in popularity, especially for Single Page Applications (SPAs).
When a user authenticates using their username and password, they're issued a token, containing an authentication ticket that can be used for authentication and authorization. The token is stored as a cookie that's sent with every request the client makes. Generating and validating this cookie is performed by the Cookie Authentication Middleware. The middleware serializes a user principal into an encrypted cookie. On subsequent requests, the middleware validates the cookie, recreates the principal, and assigns the principal to the HttpContext.User property.
When a user is authenticated, they're issued a token (not an antiforgery token). The token contains user information in the form of claims or a reference token that points the app to user state maintained in the app. When a user attempts to access a resource that requires authentication, the token is sent to the app with an extra authorization header in the form of a Bearer token. This approach makes the app stateless. In each subsequent request, the token is passed in the request for server-side validation. This token isn't encrypted; it's encoded. On the server, the token is decoded to access its information. To send the token on subsequent requests, store the token in the browser's local storage. Don't be concerned about CSRF vulnerability if the token is stored in the browser's local storage. CSRF is a concern when the token is stored in a cookie. For more information, see the GitHub issue SPA code sample adds two cookies.
Shared hosting environments are vulnerable to session hijacking, login CSRF, and other attacks.
Although example1.contoso.net
and example2.contoso.net
are different hosts, there's an implicit trust relationship between hosts under the *.contoso.net
domain. This implicit trust relationship allows potentially untrusted hosts to affect each other's cookies (the same-origin policies that govern AJAX requests don't necessarily apply to HTTP cookies).
Attacks that exploit trusted cookies between apps hosted on the same domain can be prevented by not sharing domains. When each app is hosted on its own domain, there's no implicit cookie trust relationship to exploit.
Warning
ASP.NET Core implements antiforgery using ASP.NET Core Data Protection. The data protection stack must be configured to work in a server farm. For more information, see Configuring data protection.
Antiforgery middleware is added to the Dependency injection container when one of the following APIs is called in Program.cs
:
The FormTagHelper injects antiforgery tokens into HTML form elements. The following markup in a Razor file automatically generates antiforgery tokens:
<form method="post">
<!-- ... -->
</form>
Similarly, IHtmlHelper.BeginForm generates antiforgery tokens by default if the form's method isn't GET.
The automatic generation of antiforgery tokens for HTML form elements happens when the <form>
tag contains the method="post"
attribute and either of the following are true:
action=""
).<form method="post">
).Automatic generation of antiforgery tokens for HTML form elements can be disabled:
Explicitly disable antiforgery tokens with the asp-antiforgery
attribute:
<form method="post" asp-antiforgery="false">
<!-- ... -->
</form>
The form element is opted-out of Tag Helpers by using the Tag Helper ! opt-out symbol:
<!form method="post">
<!-- ... -->
</!form>
Remove the FormTagHelper
from the view. The FormTagHelper
can be removed from a view by adding the following directive to the Razor view:
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages are automatically protected from XSRF/CSRF. For more information, see XSRF/CSRF and Razor Pages.
The most common approach to defending against CSRF attacks is to use the Synchronizer Token Pattern (STP). STP is used when the user requests a page with form data:
The token is unique and unpredictable. The token can also be used to ensure proper sequencing of a series of requests (for example, ensuring the request sequence of: page 1 > page 2 > page 3). All of the forms in ASP.NET Core MVC and Razor Pages templates generate antiforgery tokens. The following pair of view examples generates antiforgery tokens:
<form asp-action="Index" asp-controller="Home" method="post">
<!-- ... -->
</form>
@using (Html.BeginForm("Index", "Home"))
{
<!-- ... -->
}
Explicitly add an antiforgery token to a <form>
element without using Tag Helpers with the HTML helper @Html.AntiForgeryToken
:
<form asp-action="Index" asp-controller="Home" method="post">
@Html.AntiForgeryToken()
<!-- ... -->
</form>
In each of the preceding cases, ASP.NET Core adds a hidden form field similar to the following example:
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core includes three filters for working with antiforgery tokens:
Calling AddControllers does not enable antiforgery tokens. AddControllersWithViews must be called to have built-in antiforgery token support.
With the Synchronizer Token Pattern, only the most recently loaded page contains a valid antiforgery token. Using multiple tabs can be problematic. For example, if a user opens multiple tabs:
Antiforgery token validation failed. The antiforgery cookie token and request token do not match
Consider alternative CSRF protection patterns if this poses an issue.
Customize AntiforgeryOptions in Program.cs
:
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
Set the antiforgery Cookie
properties using the properties of the CookieBuilder class, as shown in the following table.
Option | Description |
---|---|
Cookie | Determines the settings used to create the antiforgery cookies. |
FormFieldName | The name of the hidden form field used by the antiforgery system to render antiforgery tokens in views. |
HeaderName | The name of the header used by the antiforgery system. If null , the system considers only form data. |
SuppressXFrameOptionsHeader | Specifies whether to suppress generation of the X-Frame-Options header. By default, the header is generated with a value of "SAMEORIGIN". Defaults to false . |
For more information, see CookieAuthenticationOptions.
IAntiforgery provides the API to configure antiforgery features. IAntiforgery
can be requested in Program.cs
using WebApplication.Services. The following example uses middleware from the app's home page to generate an antiforgery token and send it in the response as a cookie:
app.UseRouting();
app.UseAuthorization();
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
The preceding example sets a cookie named XSRF-TOKEN
. The client can read this cookie and provide its value as a header attached to AJAX requests. For example, Angular includes built-in XSRF protection that reads a cookie named XSRF-TOKEN
by default.
The ValidateAntiForgeryToken action filter can be applied to an individual action, a controller, or globally. Requests made to actions that have this filter applied are blocked unless the request includes a valid antiforgery token:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
The ValidateAntiForgeryToken
attribute requires a token for requests to the action methods it marks, including HTTP GET requests. If the ValidateAntiForgeryToken
attribute is applied across the app's controllers, it can be overridden with the IgnoreAntiforgeryToken
attribute.
Instead of broadly applying the ValidateAntiForgeryToken
attribute and then overriding it with IgnoreAntiforgeryToken
attributes, the AutoValidateAntiforgeryToken attribute can be used. This attribute works identically to the ValidateAntiForgeryToken
attribute, except that it doesn't require tokens for requests made using the following HTTP methods:
We recommend use of AutoValidateAntiforgeryToken
broadly for non-API scenarios. This attribute ensures POST actions are protected by default. The alternative is to ignore antiforgery tokens by default, unless ValidateAntiForgeryToken
is applied to individual action methods. It's more likely in this scenario for a POST action method to be left unprotected by mistake, leaving the app vulnerable to CSRF attacks. All POSTs should send the antiforgery token.
APIs don't have an automatic mechanism for sending the non-cookie part of the token. The implementation probably depends on the client code implementation. Some examples are shown below:
Class-level example:
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
Global example:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
The IgnoreAntiforgeryToken filter is used to eliminate the need for an antiforgery token for a given action (or controller). When applied, this filter overrides ValidateAntiForgeryToken
and AutoValidateAntiforgeryToken
filters specified at a higher level (globally or on a controller).
[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
// ...
return RedirectToAction();
}
Tokens should be refreshed after the user is authenticated by redirecting the user to a view or Razor Pages page.
In traditional HTML-based apps, antiforgery tokens are passed to the server using hidden form fields. In modern JavaScript-based apps and SPAs, many requests are made programmatically. These AJAX requests may use other techniques (such as request headers or cookies) to send the token.
If cookies are used to store authentication tokens and to authenticate API requests on the server, CSRF is a potential problem. If local storage is used to store the token, CSRF vulnerability might be mitigated because values from local storage aren't sent automatically to the server with every request. Using local storage to store the antiforgery token on the client and sending the token as a request header is a recommended approach.
Using JavaScript with views, the token can be created using a service from within the view. Inject the IAntiforgery service into the view and call GetAndStoreTokens:
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@{
ViewData["Title"] = "JavaScript";
var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}
<input id="RequestVerificationToken" type="hidden" value="@requestToken" />
<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>
@section Scripts {
<script>
document.addEventListener("DOMContentLoaded", () => {
const resultElement = document.getElementById("result");
document.getElementById("button").addEventListener("click", async () => {
const response = await fetch("@Url.Action("FetchEndpoint")", {
method: "POST",
headers: {
RequestVerificationToken:
document.getElementById("RequestVerificationToken").value
}
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
});
});
</script>
}
The preceding example uses JavaScript to read the hidden field value for the AJAX POST header.
This approach eliminates the need to deal directly with setting cookies from the server or reading them from the client. However, when injecting the IAntiforgery service is not possible, JavaScript can also access token in cookies, obtained from an additional request to the server (usually same-origin
), and use the cookie's contents to create a header with the token's value.
Assuming the script sends the token in a request header called X-XSRF-TOKEN
, configure the antiforgery service to look for the X-XSRF-TOKEN
header:
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
The following example adds a protected endpoint that will write the request token to a JavaScript-readable cookie:
app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
var tokens = forgeryService.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
return Results.Ok();
}).RequireAuthorization();
The following example uses JavaScript to make an AJAX request to obtain the token and make another request with the appropriate header:
var response = await fetch("/antiforgery/token", {
method: "GET",
headers: { "Authorization": authorizationToken }
});
if (response.ok) {
// https://developer.mozilla.org/docs/web/api/document/cookie
const xsrfToken = document.cookie
.split("; ")
.find(row => row.startsWith("XSRF-TOKEN="))
.split("=")[1];
response = await fetch("/JavaScript/FetchEndpoint", {
method: "POST",
headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
});
if (response.ok) {
resultElement.innerText = await response.text();
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
} else {
resultElement.innerText = `Request Failed: ${response.status}`
}
When using Windows Authentication, application endpoints must be protected against CSRF attacks in the same way as done for cookies. The browser implicitly sends the authentication context to the server and so endpoints need to be protected against CSRF attacks.
The IAntiforgeryAdditionalDataProvider type allows developers to extend the behavior of the anti-CSRF system by round-tripping additional data in each token. The GetAdditionalData method is called each time a field token is generated, and the return value is embedded within the generated token. An implementer could return a timestamp, a nonce, or any other value and then call ValidateAdditionalData to validate this data when the token is validated. The client's username is already embedded in the generated tokens, so there's no need to include this information. If a token includes supplemental data but no IAntiForgeryAdditionalDataProvider
is configured, the supplemental data isn't validated.
Cross-site request forgery (also known as XSRF or CSRF) is an attack against web-hosted apps whereby a malicious web app can influence the interaction between a client browser and a web app that trusts that browser. These attacks are possible because web browsers send some types of authentication tokens automatically with every request to a website. This form of exploit is also known as a one-click attack or session riding because the attack takes advantage of the user's previously authenticated session.
An example of a CSRF attack:
A user signs into www.good-banking-site.example.com
using forms authentication. The server authenticates the user and issues a response that includes an authentication cookie. The site is vulnerable to attack because it trusts any request that it receives with a valid authentication cookie.
The user visits a malicious site, www.bad-crook-site.example.com
.
The malicious site, www.bad-crook-site.example.com
, contains an HTML form similar to the following example:
<h1>Congratulations! You're a Winner!</h1>
<form action="https://good-banking-site.com/api/account" method="post">
<input type="hidden" name="Transaction" value="withdraw" />
<input type="hidden" name="Amount" value="1000000" />
<input type="submit" value="Click to collect your prize!" />
</form>
Notice that the form's action
posts to the vulnerable site, not to the malicious site. This is the "cross-site" part of CSRF.
The user selects the submit button. The browser makes the request and automatically includes the authentication cookie for the requested domain, www.good-banking-site.example.com
.
The request runs on the www.good-banking-site.example.com
server with the user's authentication context and can perform any action that an authenticated user is allowed to perform.
In addition to the scenario where the user selects the button to submit the form, the malicious site could:
These alternative scenarios don't require any action or input from the user other than initially visiting the malicious site.
Using HTTPS doesn't prevent a CSRF attack. The malicious site can send an https://www.good-banking-site.com/
request just as easily as it can send an insecure request.
Some attacks target endpoints that respond to GET requests, in which case an image tag can be used to perform the action. This form of attack is common on forum sites that permit images but block JavaScript. Apps that change state on GET requests, where variables or resources are altered, are vulnerable to malicious attacks. GET requests that change state are insecure. A best practice is to never change state on a GET request.
CSRF attacks are possible against web apps that use cookies for authentication because:
However, CSRF attacks aren't limited to exploiting cookies. For example, Basic and Digest authentication are also vulnerable. After a user signs in with Basic or Digest authentication, the browser automatically sends the credentials until the session ends.
In this context, session refers to the client-side session during which the user is authenticated. It's unrelated to server-side sessions or ASP.NET Core Session Middleware.
Users can protect against CSRF vulnerabilities by taking precautions:
However, CSRF vulnerabilities are fundamentally a problem with the web app, not the end user.
Cookie-based authentication is a popular form of authentication. Token-based authentication systems are growing in popularity, especially for Single Page Applications (SPAs).
When a user authenticates using their username and password, they're issued a token, containing an authentication ticket that can be used for authentication and authorization. The token is stored as a cookie that's sent with every request the client makes. Generating and validating this cookie is performed by the Cookie Authentication Middleware. The middleware serializes a user principal into an encrypted cookie. On subsequent requests, the middleware validates the cookie, recreates the principal, and assigns the principal to the HttpContext.User property.
When a user is authenticated, they're issued a token (not an antiforgery token). The token contains user information in the form of claims or a reference token that points the app to user state maintained in the app. When a user attempts to access a resource that requires authentication, the token is sent to the app with an extra authorization header in the form of a Bearer token. This approach makes the app stateless. In each subsequent request, the token is passed in the request for server-side validation. This token isn't encrypted; it's encoded. On the server, the token is decoded to access its information. To send the token on subsequent requests, store the token in the browser's local storage. Don't be concerned about CSRF vulnerability if the token is stored in the browser's local storage. CSRF is a concern when the token is stored in a cookie. For more information, see the GitHub issue SPA code sample adds two cookies.
Shared hosting environments are vulnerable to session hijacking, login CSRF, and other attacks.
Although example1.contoso.net
and example2.contoso.net
are different hosts, there's an implicit trust relationship between hosts under the *.contoso.net
domain. This implicit trust relationship allows potentially untrusted hosts to affect each other's cookies (the same-origin policies that govern AJAX requests don't necessarily apply to HTTP cookies).
Attacks that exploit trusted cookies between apps hosted on the same domain can be prevented by not sharing domains. When each app is hosted on its own domain, there's no implicit cookie trust relationship to exploit.
Warning
ASP.NET Core implements antiforgery using ASP.NET Core Data Protection. The data protection stack must be configured to work in a server farm. For more information, see Configuring data protection.
Antiforgery middleware is added to the Dependency injection container when one of the following APIs is called in Startup.ConfigureServices
:
In ASP.NET Core 2.0 or later, the FormTagHelper injects antiforgery tokens into HTML form elements. The following markup in a Razor file automatically generates antiforgery tokens:
<form method="post">
...
</form>
Similarly, IHtmlHelper.BeginForm generates antiforgery tokens by default if the form's method isn't GET.
The automatic generation of antiforgery tokens for HTML form elements happens when the <form>
tag contains the method="post"
attribute and either of the following are true:
action=""
).<form method="post">
).Automatic generation of antiforgery tokens for HTML form elements can be disabled:
Explicitly disable antiforgery tokens with the asp-antiforgery
attribute:
<form method="post" asp-antiforgery="false">
...
</form>
The form element is opted-out of Tag Helpers by using the Tag Helper ! opt-out symbol:
<!form method="post">
...
</!form>
Remove the FormTagHelper
from the view. The FormTagHelper
can be removed from a view by adding the following directive to the Razor view:
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
Note
Razor Pages are automatically protected from XSRF/CSRF. For more information, see XSRF/CSRF and Razor Pages.
The most common approach to defending against CSRF attacks is to use the Synchronizer Token Pattern (STP). STP is used when the user requests a page with form data:
The token is unique and unpredictable. The token can also be used to ensure proper sequencing of a series of requests (for example, ensuring the request sequence of: page 1 > page 2 > page 3). All of the forms in ASP.NET Core MVC and Razor Pages templates generate antiforgery tokens. The following pair of view examples generates antiforgery tokens:
<form asp-controller="Todo" asp-action="Create" method="post">
...
</form>
@using (Html.BeginForm("Create", "Todo"))
{
...
}
Explicitly add an antiforgery token to a <form>
element without using Tag Helpers with the HTML helper @Html.AntiForgeryToken
:
<form action="/" method="post">
@Html.AntiForgeryToken()
</form>
In each of the preceding cases, ASP.NET Core adds a hidden form field similar to the following example:
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core includes three filters for working with antiforgery tokens:
Customize AntiforgeryOptions in Startup.ConfigureServices
:
services.AddAntiforgery(options =>
{
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
Set the antiforgery Cookie
properties using the properties of the CookieBuilder class, as shown in the following table.
Option | Description |
---|---|
Cookie | Determines the settings used to create the antiforgery cookies. |
FormFieldName | The name of the hidden form field used by the antiforgery system to render antiforgery tokens in views. |
HeaderName | The name of the header used by the antiforgery system. If null , the system considers only form data. |
SuppressXFrameOptionsHeader | Specifies whether to suppress generation of the X-Frame-Options header. By default, the header is generated with a value of "SAMEORIGIN". Defaults to false . |
For more information, see CookieAuthenticationOptions.
IAntiforgery provides the API to configure antiforgery features. IAntiforgery
can be requested in the Configure
method of the Startup
class.
In the following example:
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
}
ValidateAntiForgeryToken is an action filter that can be applied to an individual action, a controller, or globally. Requests made to actions that have this filter applied are blocked unless the request includes a valid antiforgery token.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
ManageMessageId? message = ManageMessageId.Error;
var user = await GetCurrentUserAsync();
if (user != null)
{
var result =
await _userManager.RemoveLoginAsync(
user, account.LoginProvider, account.ProviderKey);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}
return RedirectToAction(nameof(ManageLogins), new { Message = message });
}
The ValidateAntiForgeryToken
attribute requires a token for requests to the action methods it marks, including HTTP GET requests. If the ValidateAntiForgeryToken
attribute is applied across the app's controllers, it can be overridden with the IgnoreAntiforgeryToken
attribute.
Note
ASP.NET Core doesn't support adding antiforgery tokens to GET requests automatically.
ASP.NET Core apps don't generate antiforgery tokens for safe HTTP methods (GET, HEAD, OPTIONS, and TRACE). Instead of broadly applying the ValidateAntiForgeryToken
attribute and then overriding it with IgnoreAntiforgeryToken
attributes, the AutoValidateAntiforgeryToken attribute can be used. This attribute works identically to the ValidateAntiForgeryToken
attribute, except that it doesn't require tokens for requests made using the following HTTP methods:
We recommend use of AutoValidateAntiforgeryToken
broadly for non-API scenarios. This attribute ensures POST actions are protected by default. The alternative is to ignore antiforgery tokens by default, unless ValidateAntiForgeryToken
is applied to individual action methods. It's more likely in this scenario for a POST action method to be left unprotected by mistake, leaving the app vulnerable to CSRF attacks. All POSTs should send the antiforgery token.
APIs don't have an automatic mechanism for sending the non-cookie part of the token. The implementation probably depends on the client code implementation. Some examples are shown below:
Class-level example:
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
Global example:
services.AddControllersWithViews(options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
The IgnoreAntiforgeryToken filter is used to eliminate the need for an antiforgery token for a given action (or controller). When applied, this filter overrides ValidateAntiForgeryToken
and AutoValidateAntiforgeryToken
filters specified at a higher level (globally or on a controller).
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
{
// no antiforgery token required
}
}
Tokens should be refreshed after the user is authenticated by redirecting the user to a view or Razor Pages page.
In traditional HTML-based apps, antiforgery tokens are passed to the server using hidden form fields. In modern JavaScript-based apps and SPAs, many requests are made programmatically. These AJAX requests may use other techniques (such as request headers or cookies) to send the token.
If cookies are used to store authentication tokens and to authenticate API requests on the server, CSRF is a potential problem. If local storage is used to store the token, CSRF vulnerability might be mitigated because values from local storage aren't sent automatically to the server with every request. Using local storage to store the antiforgery token on the client and sending the token as a request header is a recommended approach.
Using JavaScript with views, the token can be created using a service from within the view. Inject the IAntiforgery service into the view and call GetAndStoreTokens:
@{
ViewData["Title"] = "AJAX Demo";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<input type="hidden" id="RequestVerificationToken"
name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<div class="row">
<p><input type="button" id="antiforgery" value="Antiforgery"></p>
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == XMLHttpRequest.DONE) {
if (xhttp.status == 200) {
alert(xhttp.responseText);
} else {
alert('There was an error processing the AJAX request.');
}
}
};
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("antiforgery").onclick = function () {
xhttp.open('POST', '@Url.Action("Antiforgery", "Home")', true);
xhttp.setRequestHeader("RequestVerificationToken",
document.getElementById('RequestVerificationToken').value);
xhttp.send();
}
});
</script>
</div>
This approach eliminates the need to deal directly with setting cookies from the server or reading them from the client.
The preceding example uses JavaScript to read the hidden field value for the AJAX POST header.
JavaScript can also access tokens in cookies and use the cookie's contents to create a header with the token's value.
context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,
new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });
Assuming the script requests to send the token in a header called X-CSRF-TOKEN
, configure the antiforgery service to look for the X-CSRF-TOKEN
header:
services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");
The following example uses JavaScript to make an AJAX request with the appropriate header:
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
var csrfToken = getCookie("CSRF-TOKEN");
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (xhttp.readyState === XMLHttpRequest.DONE) {
if (xhttp.status === 204) {
alert('Todo item is created successfully.');
} else {
alert('There was an error processing the AJAX request.');
}
}
};
xhttp.open('POST', '/api/items', true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-CSRF-TOKEN", csrfToken);
xhttp.send(JSON.stringify({ "name": "Learn C#" }));
AngularJS uses a convention to address CSRF. If the server sends a cookie with the name XSRF-TOKEN
, the AngularJS $http
service adds the cookie value to a header when it sends a request to the server. This process is automatic. The client doesn't need to set the header explicitly. The header name is X-XSRF-TOKEN
. The server should detect this header and validate its contents.
For ASP.NET Core API to work with this convention in your application startup:
XSRF-TOKEN
.X-XSRF-TOKEN
, which is Angular's default header name for sending the XSRF token.public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
app.Use(next => context =>
{
string path = context.Request.Path.Value;
if (
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
}
Note
When the antiforgery token is provided in both the request header and in the form payload, only the token in the header is validated.
When using Windows Authentication, application endpoints must be protected against CSRF attacks in the same way as done for cookies. The browser implicitly sends the authentication context to the server and so endpoints need to be protected against CSRF attacks.
The IAntiforgeryAdditionalDataProvider type allows developers to extend the behavior of the anti-CSRF system by round-tripping additional data in each token. The GetAdditionalData method is called each time a field token is generated, and the return value is embedded within the generated token. An implementer could return a timestamp, a nonce, or any other value and then call ValidateAdditionalData to validate this data when the token is validated. The client's username is already embedded in the generated tokens, so there's no need to include this information. If a token includes supplemental data but no IAntiForgeryAdditionalDataProvider
is configured, the supplemental data isn't validated.
ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Training
Module
Secure a .NET web app with the ASP.NET Core Identity framework - Training
Learn how to add authentication and authorization to a .NET web app using the ASP.NET Core Identity framework.