ValidateAntiForgeryToken in Ajax request with AspNet Core MVC

前端 未结 3 1258
囚心锁ツ
囚心锁ツ 2021-02-14 00:18

I have been trying to recreate an Ajax version of the ValidateAntiForgeryToken - there are many blog posts on how to do this for previous versions of MVC, but with the latest MV

3条回答
  •  难免孤独
    2021-02-14 00:33

    I've been wrestling with a similar situation, interfacing angular POSTs with MVC6, and came up with the following.

    There are two problems that need to be addressed: getting the security token into MVC's antiforgery validation subsystem, and translating angular's JSON-formatted postback data into an MVC model.

    I handle the first step via some custom middleware inserted in Startup.Configure(). The middleware class is pretty simple:

    public static class UseAngularXSRFExtension
    {
        public const string XSRFFieldName = "X-XSRF-TOKEN";
    
        public static IApplicationBuilder UseAngularXSRF( this IApplicationBuilder builder )
        {
            return builder.Use( next => context =>
            {
                switch( context.Request.Method.ToLower() )
                {
                    case "post":
                    case "put":
                    case "delete":
                        if( context.Request.Headers.ContainsKey( XSRFFieldName ) )
                        {
                            var formFields = new Dictionary()
                            {
                                { XSRFFieldName, context.Request.Headers[XSRFFieldName] }
                            };
    
                            // this assumes that any POST, PUT or DELETE having a header
                            // which includes XSRFFieldName is coming from angular, so 
                            // overwriting context.Request.Form is okay (since it's not
                            // being parsed by MVC's internals anyway)
                            context.Request.Form = new FormCollection( formFields );
                        }
    
                        break;
                }
    
                return next( context );
            } );
        }
    }
    

    You insert this into the pipeline with the following line inside the Startup.Configure() method:

    app.UseAngularXSRF();
    

    I did this right before the call to app.UseMVC().

    Note that this extension transfers the XSRF header on any POST, PUT or DELETE where it exists, and it does so by overwriting the existing form field collection. That fits my design pattern -- the only time the XSRF header will be in a request is if it's coming from some angular code I've written -- but it may not fit yours.

    I also think you need to configure the antiforgery subsystem to use the correct name for the XSRF field name (I'm not sure what the default is). You can do this by inserting the following line into Startup.ConfigureServices():

        services.ConfigureAntiforgery( options => options.FormFieldName = UseAngularXSRFExtension.XSRFFieldName );
    

    I inserted this right before the line services.AddAntiforgery().

    There are several ways of getting the XSRF token into the request stream. What I do is add the following to the view:

    ...top of view...
    @inject Microsoft.AspNet.Antiforgery.IAntiforgery af
    ...rest of view...
    
    ...inside the angular function...
                var postHeaders = {
                    'X-XSRF-TOKEN': '@(af.GetTokens(this.Context).FormToken)',
                    'Content-Type': 'application/json; charset=utf-8',
                };
    
                $http.post( '/Dataset/DeleteDataset', JSON.stringify({ 'siteID': siteID }),
                    {
                        headers: postHeaders,
                    })
    ...rest of view...
    

    The second part -- translating the JSON data -- is handled by decorating the model class on your action method with [FromBody]:

            // the [FromBody] attribute on the model -- and a class model, rather than a
            // single integer model -- are necessary so that MVC can parse the JSON-formatted
            // text POSTed by angular
            [HttpPost]
            [ValidateAntiForgeryToken]
            public IActionResult DeleteDataset( [FromBody] DeleteSiteViewModel model )
            {
    }
    

    [FromBody] only works on class instances. Even though in my case all I'm interested in is a single integer, I still had to dummy up a class, which only contains a single integer property.

    Hope this helps.

提交回复
热议问题