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.jsvue-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
}
};
Was running into the same issue. My application backend is Django with graphene. My frontend is React. I also had 2 issues:
i didn't use the correct graphql url in my frontend apollo createHttpLink. In my django urls.py, my graphql url had "/" but i didn't put "/" in frontend. Just make sure the urls match exactly.
In addition to setting csrf token to header, you also have to set the csrf in Cookie object otherwise you will get a forbidden error. See [this][https://github.com/graphql-python/graphene-django/issues/786]. When the CSRF_USE_SESSIONS settings variable is set to True post requests can not be made as the request will be rejected. This is because the CSRFTOKEN is not provided, because it will not be stored in a cookie.Django will end up giving this warning: Forbidden (CSRF token missing or incorrect.): This issue can be prevented by passing the view to csrf_exempt in django's urls.py file.
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True)))
However a better solution is to store csrf in global cookie in the frontend in addition to setting it in the header like this:
const csrftoken = await getCsrfToken();
const cookies = new Cookies();
cookies.set('csrftoken', csrftoken);
(See my index.js code below for full code).
My index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './custom.scss'
import allReducers from './reducer';
import {Provider} from 'react-redux';
import {BrowserRouter, Route, Routes} from "react-router-dom";
import {
ApolloClient,
InMemoryCache,
ApolloProvider, from, createHttpLink
} from "@apollo/client";
import {createStore} from "redux";
import {AuthProvider} from "./utils/auth";
import {setContext} from "@apollo/client/link/context";
import {ACCESS_TOKEN_KEY} from "./constants/Constants";
import {onError} from "@apollo/client/link/error";
import Cookies from "universal-cookie/es6";
const store = createStore(allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
let csrftoken;
async function getCsrfToken() {
if (csrftoken) return csrftoken;
csrftoken = await fetch('http://localhost:8000/csrf/')
.then(response => response.json())
.then(data => data.csrfToken)
return await csrftoken
}
const authMiddleware = setContext(async (req, { headers }) => {
const token = localStorage.getItem(ACCESS_TOKEN_KEY);
const csrftoken = await getCsrfToken();
const cookies = new Cookies();
cookies.set('csrftoken', csrftoken);
return {
headers: {
...headers,
'X-CSRFToken': csrftoken,
Authorization: token ? `Bearer ${token}` : ''
},
};
});
const httpLink = createHttpLink({
uri: 'http://localhost:8000/graphql/',
credentials: 'include'
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
const client2 = new ApolloClient({
uri: 'http://localhost:8000/graphql/',
cache: new InMemoryCache(),
credentials: 'include',
link: from([authMiddleware, errorLink, httpLink])
});
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<ApolloProvider client={client2}>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/*" element={<App/>} />
</Routes>
</AuthProvider>
</BrowserRouter>
</ApolloProvider>,
</React.StrictMode></Provider>,
document.getElementById('root')
);
On the backend, here are some of the related files:
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql/', GraphQLView.as_view(graphiql=True)),
path('csrf/', csrf),
]
settings.py:
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ["http://localhost:3000", ]
CSRF_TRUSTED_ORIGINS = ["http://localhost:3000", ]
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]
views.py (returns generated csrf token to frontend)
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.shortcuts import render
# Create your views here.
def csrf(request):
return JsonResponse({'csrfToken': get_token(request)})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With