ASP.NET MVC – Smart Way to Prevent Cross-Site Request Forgery (CSRF) Attempts – WebAPI (AJAX XHR) and Normal POST Operations

Monday, August 12th, 2019

ASP.NET MVC – The Smart Way to Prevent Cross-Site Request Forgery (CSRF) Attempts

WebAPI (AJAX XHR) and Normal POST Operations

If your ASP.NET MVC application uses some WebAPI endpoints which are called using XHR (AJAX) requests from clientside JavaScript, you can still protect against CSRF attacks by validating the origin of such a request (when it is an AJAX request) or perform the default action of validating the anti-CSRF token (for POST form requests).

I modified the below code from https://stackoverflow.com/questions/35085507/set-validateantiforgerytoken-attribute-to-get-post-for-same-action-mvc5#answer-35085970 or ARCHIVE

using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Web.Helpers;
using System.Linq;
using System.Collections.Generic;
using System.Configuration;
namespace System.Web.Mvc
{
    /// <summary>
    /// Cross-Site Request Forgery (CSRF) Prevention Filter for WebAPI and Normal MVC Controllers
    /// Normal POST operations = token is checked
    /// Normal controller GET operations = ignored
    /// WebAPI requests = check to make sure they were initiated by an AJAX request from a trusted origin
    /// </summary>    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class ValidateAntiForgeryTokenPOSTOrAJAXOrigin : FilterAttribute, IAuthorizationFilter
    {
        private string _salt;        

        public ValidateAntiForgeryTokenAttribute2() : this(AntiForgery.Validate)
        {
        }        

        internal ValidateAntiForgeryTokenAttribute2(Action validateAction)
        {
            Debug.Assert(validateAction != null);
            ValidateAction = validateAction;
        }
        
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
        [Obsolete("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.", error: true)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        
        public string Salt
        {
            get { return _salt; }
            set
            {
                if (!String.IsNullOrEmpty(value))
                {
                    throw new NotSupportedException("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.");
                }
                _salt = value;
            }
        }

        internal Action ValidateAction { get; private set; }        
        
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            string validOrigins = ConfigurationManager.AppSettings["AllowedEnvironments"]; // Example in web.config <add key="AllowedEnvironments" value="https://testurl.com:4443,https://testurl.com,https://testurl2.com" />
            bool skipCheck = false;
            
            if(ConfigurationManager.AppSettings["LocalDevMode"] == "1")
            {
                skipCheck = true;
            }
            
            // In AJAX requests, the origin header is always sent (UNLESS IT'S COMING FROM THE SAME ORIGIN), so we can validate that it comes from a trusted location to prevent CSRF attacks - but if one isn't sent, we won't do anything (assume trusted)
            // In which case, we don't need to do any token checking either 🙂
            if(!skipCheck && !string.IsNullOrEmpty(validOrigins))
            {
                List<string> validOriginURLs = validOrigins.Split(',').ToList();
                if(filterContext.HttpContext.Request.Headers["Origin"] != null && !string.IsNullOrEmpty(filterContext.HttpContext.Request.Headers["Origin"].ToString()))
                {
                    string origin = filterContext.HttpContext.Request.Headers["Origin"];
                    if(!validOriginURLs.Contains(origin))
                    {
                        filterContext.Result = new RedirectResult("~/Home/InvalidRequest");
                        skipCheck = true; // Still set to true to prevent additional validation
                    }
                    else
                    {
                        skipCheck = true;
                    }
                }
            }
        
            if(!skipCheck){
                var request = filterContext.HttpContext.Request.HttpMethod;
                if (request != "GET" && (!filterContext.HttpContext.Request.IsAjaxRequest() || (filterContext.HttpContext.Request.IsAjaxRequest() && (filterContext.HttpContext.Request.Headers["X-Request-With"] == null || filterContext.HttpContext.Request.Headers["X-Requested-With"] != "XMLHttpRequest"))))
                {
                    // Do normal form POST antiforgery token check
                    if (filterContext == null)
                    {
                        throw new ArgumentNullException("filterContext");
                    }                    try
                    {
                        ValidateAction();
                    }
                    catch(Exception e)
                    {
                        filterContext.Result = new RedirectResult("~/Home/InvalidRequest");
                    }
                }
            }
        }
    }
}

 

Multiple Comment Forms on WordPress Page – Using WYSIWYG Editor NicEdit

Tuesday, January 28th, 2014

WYSIWYG WordPress Comments

I tried and tried to get the CKEditor plugin for WordPress to initalize multiple comment textareas for a custom post type I had created using the pods WordPress plugin.  No matter how hard I tried, I failed.  Only the first instance would get initialized.  I dug through the code and found where the instance was created, but even with the code in front of my eyes, I could not figure out how to adapt the messy PHP / Javascript mesh to do what I wanted.

Eventually, I came accross a great guide explaining how to use NicEdit, a simple WYSIWYG editor that can be used easily to replace the boring WordPress comment box with something more friendly for end users leaving comments.

Following this guide, I downloaded and uploaded the NicEdit files to a folder named "scripts" in my theme's directory (please adjust the path in the functions below if you want to use another directory or place the files elsewhere).  I would recommend downloading the non-compressed version, as the code is easier to read and making changes is a lot easier.  I inserted the following function into my WordPress theme's functions.php file:

function add_nicEdit() {
    if ( !is_admin() ) {
        // register scripts
        wp_register_script('nicEdit', get_stylesheet_directory_uri() . '/scripts/nicEdit.js', array(), false, true);
 
        // enqueue scripts
        wp_enqueue_script('nicEdit');
    }
}
 
add_action('wp_enqueue_scripts', 'add_nicEdit');

Then, I created a new function that I added to NicEdit.js file.  After this code:

allTextAreas : function(nicOptions) {
        var textareas = document.getElementsByTagName("textarea");
        for(var i=0;i<textareas.length;i++) {
            nicEditors.editors.push(new nicEditor(nicOptions).panelInstance(textareas[i]));
        }
        return nicEditors.editors;
    },

I added a function called allCommentTextAreas:

allCommentTextAreas : function(nicOptions) {
        var textareas = document.getElementsByTagName("textarea");
        for(var i=0;i<textareas.length;i++) {
      if(textareas[i].className == "comment_editor"){
               nicEditors.editors.push(new nicEditor(nicOptions).panelInstance(textareas[i]));
      }
        }
        return nicEditors.editors;
    },

The way this function works is that it will replace all textareas that have a class of "comment_editor" only.

Next, in my theme's footer.php file after the <?php wp_footer(); ?> line, I added the following:

<script type="text/javascript">
           // We need to initialize the WYSIWIG editor for the comments field                   bkLib.onDomLoaded(function() { nicEditors.allCommentTextAreas({
                   iconsPath : '<?php bloginfo( 'stylesheet_directory' ) ?>/scripts/nicEditorIcons.gif', buttonList : ['bold','italic','strikethrough','link','unlink','removeformat']})
                   });
        </script>

In your pods page template, load comments like this:

$args["comment_notes_after"] = "";
$args["comment_field"] = '<p class="comment-form-comment"><label for="comment">' . _x( 'Comment', 'noun' ) .          
'</label><textarea id="comment" class="comment_editor" name="comment" cols="45" rows="8" aria-required="true">' .          
'</textarea></p>';
comment_form( $args, $post_id ); 

Your comments form will now look a lot better, specifically, they will look like this:

nic edit