djangographqlgraphene-pythonvue-apollographene-django

Does a graphene django Endpoint expects a X-Csrftoken and CsrfCookie at the same time?


Using:


I am testing single page vue app´s with Django, GraphQL & Vue-Apollo.

If i use csrf_exempt on my view everything works in the frontend.

urlpatterns = [
<...>
   path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
<...>

Now i wanted to CSRF protect my request. Within the process of understanding the CSRF protection, i thought all Django GraphQLView needs is to receive the "value" of the X-Csrftoken in the Request Header. So i focused on sending the csrf Value in different ways...via a single view like this

path('csrf/', views.csrf),
path("graphql", GraphQLView.as_view(graphiql=True)),

or by ensure a cookie with ensure_csrf_cookie

Afterwards in my ApolloClient i fetch thes Value and send him back with the request Header .

This i what Django prints when i send a GraphQL request from a Django-Vue page.

Forbidden (CSRF token missing or incorrect.): /graphql

Parallel i always test with thegraphiql IDE and these requests still working. I also print everytime the info.context.headers value of my query resolver.

{'Content-Length': '400', 'Content-Type': 'application/json',
'Host': 'localhost:7000', 'Connection': 'keep-alive',
'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 
'Accept': 'application/json', 'Sec-Fetch-Dest': 'empty', 'X-Csrftoken': 'dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36',
'Origin': 'http://localhost:7000',
'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors',
'Referer': 'http://localhost:7000/graphql', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.9,de;q=0.8',
'Cookie': 'sessionid=jqjvjfvg4sjmp7nkeunebqos8c7onhiz; csrftoken=dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz'}

i recognized that the GraphQLView IDE alway puts the X-Csrftoken and the Cookie:..csrftoken. also in the request. if delete the csrftoken-cookie of a GraphQLView IDE before sending the request, i get this

Forbidden (CSRF cookie not set.): /graphql

The IDE shows a long, red report

.... CSRF verification failed. Request aborted.</p>\n\n\n  
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms.
This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>\n

The Information of the IDE say´s the request needs a CSRF cookie. But all read until now in Forums, Doc´s, was more related to the value itself. Meaning all you need is to send the csrf value within the Header as X-Csrftoken or so and the View would do the magic.


Question

Therefore my Question is:

Do i have to set the X-Csrftoken and the Cookie:..csrftoken at the same time in my ApolloClient to make a request on my django GraphQLView ?

Or is it also possible to simple send only the X-Csrftoken without a csrf-cookie and vice versa?


Solution

  • After long time and a pause to follow the issue, i tried once more and found a solution.

    Setup

    Presumption


    Problem Recap

    After revisiting my problem i noticed i have 2 Issue. One was the Browser denied graphQL request because of CORS. And the Second was the CSRF Token.


    Solution

    To Fix the CORS Issue i noticed that my uri of the Apollo Client was not the same as my Django Dev Server. Instead of http://127.0.0.1:7000/graphql it was set to http://localhost:7000/graphql. I also set the credentials (see vue-apollo.js)

    To Fix the CSRF i did 3 things

    vue-apollo.js


    import Vue from 'vue'
    // import path for the new Apollo Client 3 and Vue-Apollo
    import { ApolloClient, InMemoryCache } from '@apollo/client/core';
    import VueApollo from 'vue-apollo'
    import Cookies from 'js-cookie'
    
      
    // Create the apollo client
    const apolloClient = new ApolloClient({
      // -------------------
      // # Required Fields #
      // -------------------
      // URI - GraphQL Endpoint
      uri: 'http://127.0.0.1:7000/graphql',
      // Cache
      cache: new InMemoryCache(),
    
      // -------------------
      // # Optional Fields #
      // -------------------
      // DevBrowserConsole
      connectToDevTools: true,
      // Else
      credentials: 'same-origin',
      headers: {
        'X-CSRFToken': Cookies.get('csrftoken')
      }
    });
      
    // create Vue-Apollo Instance
    const apolloProvider = new VueApollo({
      defaultClient: apolloClient,
    })
      
    // Install the vue plugin
    Vue.use(VueApollo)
      
    export default apolloProvider
    

    Vue.config.js


    const BundleTracker = require("webpack-bundle-tracker");
    
    // hook your apps
    const pages = {
        'page_1': {
            entry: './src/page_1.js',
            chunks: ['chunk-vendors']
        },
        'page_2': {
            entry: './src/page_2.js',
            chunks: ['chunk-vendors']
        },
    }
    
    module.exports = {
        pages: pages,
        filenameHashing: false,
        productionSourceMap: false,
    
        // puplicPath: 
        // Tells Django where do find the bundle.
        publicPath: '/static/',
    
        // outputDir:
        // The directory where the production build files will be generated - STATICFILES_DIRS
        outputDir: '../dev_static/vue_bundle',
     
        
        chainWebpack: config => {
    
            config.optimization
            .splitChunks({
                cacheGroups: {
                    vendor: {
                        test: /[\\/]node_modules[\\/]/,
                        name: "chunk-vendors",
                        chunks: "all",
                        priority: 1
                    },
                },
            });
    
    
            // Don´t create Templates because we using Django Templates
            Object.keys(pages).forEach(page => {
                config.plugins.delete(`html-${page}`);
                config.plugins.delete(`preload-${page}`);
                config.plugins.delete(`prefetch-${page}`);
            })
    
            // create webpack-stats.json. 
            // This file will describe the bundles produced by this build process.
            // used eventually by django-webpack-loader
            config
                .plugin('BundleTracker')
                .use(BundleTracker, [{filename: '/webpack-stats.json'}]);
    
    
            // added to use ApolloQuery Tag (Apollo Components) see vue-apollo documentation
            config.module
            .rule('vue')
            .use('vue-loader')
                .loader('vue-loader')
                .tap(options => {
                options.transpileOptions = {
                    transforms: {
                    dangerousTaggedTemplateString: true,
                    },
                }
                return options
                })
            
            // This will allows us to reference paths to static 
            // files within our Vue component as <img src="~__STATIC__/logo.png">
            config.resolve.alias
                .set('__STATIC__', 'static')
    
            // configure a development server for use in non-production modes,
            config.devServer
                .public('http://localhost:8080')
                .host('localhost')
                .port(8080)
                .hotOnly(true)
                .watchOptions({poll: 1000})
                .https(false)
                .headers({"Access-Control-Allow-Origin": ["*"]})
            
            // DO have Webpack hash chunk filename
            config.output
                .chunkFilename("[id].js")
                },
    
        devServer: {
            writeToDisk: true
          }
    };