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?
After long time and a pause to follow the issue, i tried once more and found a solution.
Setup
Presumption
*vue.js
files in the Django STATICFILES_DIRS
. Django will take the Files from there. works fineProblem 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
{% csrf_token %}
with the HTML where your Vue/ GraphQL Client app is hooked. So that we can fetch it later.js-cookie
for getting the CookieX-CSRFToken
in vue-apollo.js
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
}
};