Prevent Cross-Site Request Forgery Attacks (CSRF)
- 4 minutes to read
In Cross-Site Request Forgery (CSRF) attacks, a threat actor tricks an authenticated user into executing unauthorized commands.
Use anti-forgery tokens to protect your application from CSRF attacks. These tokens work as follows:
- Once the client requests an HTML page with a form, the server generates two random tokens.
- The server adds these tokens in the response. It sends one token as an HttpOnly cookie and places another token in a hidden form field.
- Each time a user submits the form, the client sends tokens back to the server.
- If the server receives a request that does not include both tokens or if one of tokens was modified, the server rejects the request.
Note
Do not use the GET method to send data modifying requests.
Use Anti-Forgery Tokens
To add anti-forgery tokens to a Razor page:
In the view code, generate an AntiForgery token for a form:
@using(Html.BeginForm()) { @Html.AntiForgeryToken() // ... }Make certain that AJAX requests for all controls include the token value:
<script> if (window.jQuery) { $.ajaxPrefilter(function (options, originalOptions, xhr) { if (options.dataType && options.dataType !== "html") return; var tokenValue = $('input:hidden[name="__RequestVerificationToken"]').val(); if (tokenValue && options && options.data && options.data.indexOf('RequestVerificationToken') === -1) options.data += "&__RequestVerificationToken=" + tokenValue; }); } </script>To validate the request token on the server, apply the ValidateAntiForgeryToken attribute to the corresponding controller action:
[ValidateAntiForgeryToken] public ActionResult EditFormDeletePartial(int id = -1) { if(id >= 0) EditFormItems.Delete(id); return EditFormPartial(); }
Use Anti‑Forgery Tokens with Individual Controls
Note
The following steps supplement, but do not replace, the anti‑forgery token validation for page-level callbacks described above. Implement both mechanisms for complete CSRF protection.
Dashboard Designer
To protect against CSRF attacks when using the Dashboard Designer:
Inject an anti-forgery token into the Dashboard’s AJAX request header as follows:
<script type="text/javascript"> function onBeforeRender(s, e) { var dashboardControl = s.GetDashboardControl(); dashboardControl.remoteService.headers['__RequestVerificationToken'] = document.querySelector('input[name=__RequestVerificationToken]').value; } </script> @using(Html.BeginForm()) { @Html.AntiForgeryToken() @Html.DevExpress().Dashboard(settings => { settings.Name = "Dashboard"; settings.ControllerName = "DashboardWithAntiForgeryToken"; settings.InitialDashboardId = "editId"; settings.ClientSideEvents.BeforeRender = "onBeforeRender"; settings.ClientSideEvents.Init = "onBeforeRender"; }).GetHtml() }Define a custom attribute to validate the anti-forgery token on requests:
public sealed class DashboardValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationContext filterContext) { if(filterContext == null) { throw new ArgumentNullException(nameof(filterContext)); } HttpContextBase httpContext = filterContext.HttpContext; HttpCookie cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName]; AntiForgery.Validate(cookie?.Value, httpContext.Request.Headers["__RequestVerificationToken"]); } }Apply this attribute to the controller action that handles Dashboard callbacks:
[DashboardValidateAntiForgeryToken] public class DashboardWithAntiForgeryTokenController : DevExpress.DashboardWeb.Mvc.DashboardController { static readonly DashboardConfigurator dashboardConfigurator; static DashboardWithAntiForgeryTokenController() { var dashboardInMemoryStorage = new DashboardInMemoryStorage(); dashboardInMemoryStorage.RegisterDashboard("editId", XDocument.Load(HostingEnvironment.MapPath(@"~/App_Data/PublicDashboard.xml"))); dashboardConfigurator = new DashboardConfigurator(); dashboardConfigurator.SetDashboardStorage(dashboardInMemoryStorage); } public DashboardWithAntiForgeryTokenController() : base(dashboardConfigurator) { } }
Spreadsheet, Rich Text Editor, and File Management
To protect against CSRF attacks when using File Manager, File Upload, Rich Text Editor, and Spreadsheet controls:
Generate an anti-forgery token on a page:
@Html.AntiForgeryToken()Add a client function that injects the token into the
RequestVerificationTokenHTTP request header.<script> function onBeforeSend(spreadsheet, e) { e.request.setRequestHeader("RequestVerificationToken", document.querySelector('input[name=__RequestVerificationToken]').value); } </script>Pass the function name to the control’s
BeforeSendclient-side event to add the token to all HTTP requests.@Html.DevExpress().FileManager(settings => { settings.Name = "FileManager"; settings.CallbackRouteValues = new { Controller = "Home", Action = "FileManagerPartial" }; settings.ClientSideEvents.BeforeSend = "onBeforeSend"; }).BindToFolder(Model).GetHtml()Implement
IHttpModuleSubscriberto validate theRequestVerificationTokenHTTP request header against the anti‑forgery cookie token. Reject all unsafe requests that fail validation.public class AntiforgeryOfficeHttpModuleSubscriber : IHttpModuleSubscriber { bool IsUploadRequest(HttpRequest request) { return request.Path.EndsWith(DevExpress.Web.Internal .HttpUtils.UploadProgressHttpHandlerPagePath, StringComparison.OrdinalIgnoreCase); } bool IsOfficeRequest(HttpRequest request) { return request.Path.EndsWith(DevExpress.Web.Office.Internal. DocumentWorkSessionManagerSubscriber.HandlerName, StringComparison.OrdinalIgnoreCase); } public bool RequestRecipient(HttpRequest request, RequestEvent requestEvent) { return requestEvent == RequestEvent.BeginRequest && (IsOfficeRequest(request) || IsUploadRequest(request)); } public void ProcessRequest() { HttpContext context = HttpContext.Current; HttpRequest request = context.Request; if(!IsUnsafeMethod(request)) return; if(!TryValidateAntiforgery(request, out var error)) { context.Response.StatusCode = 400; context.Response.TrySkipIisCustomErrors = true; context.Response.ContentType = "text/plain"; context.Response.Write("Invalid anti-forgery token."); if(!string.IsNullOrEmpty(error)) context.Response.Write(" " + HttpUtility.HtmlEncode(error)); context.ApplicationInstance.CompleteRequest(); } } bool IsUnsafeMethod(HttpRequest r) { return !(r.HttpMethod == "GET" || r.HttpMethod == "HEAD" || r.HttpMethod == "OPTIONS" || r.HttpMethod == "TRACE"); } bool TryValidateAntiforgery(HttpRequest request, out string error) { error = null; try { string headerToken = request.Headers["RequestVerificationToken"]; string cookieToken = request.Cookies[System.Web.Helpers.AntiForgeryConfig.CookieName] ?.Value; System.Web.Helpers.AntiForgery.Validate(cookieToken, headerToken); return true; } catch(System.Web.Mvc.HttpAntiForgeryException ex) { error = ex.Message; return false; } } }Register the
AntiforgeryOfficeHttpModuleSubscriberat application startup so the DevExpress module can invoke it for matching requests.void Application_Start(object sender, EventArgs e) { ASPxHttpHandlerModule.Subscribe(new AntiforgeryOfficeHttpModuleSubscriber()); }