How to add cookies to vue-apollo request? - django

I use vue-cli-plugin-apollo and I want to send language chosen by user from frontend to backend via cookie.
As a vue-apollo.js I use the next template
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'
// Install the vue plugin
Vue.use(VueApollo)
// Name of the localStorage item
const AUTH_TOKEN = 'apollo-token'
// Http endpoint
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000/graphql'
// Files URL root
export const filesRoot = process.env.VUE_APP_FILES_ROOT || httpEndpoint.substr(0, httpEndpoint.indexOf('/graphql'))
Vue.prototype.$filesRoot = filesRoot
// Config
const defaultOptions = {
// You can use `https` for secure connection (recommended in production)
httpEndpoint,
// You can use `wss` for secure connection (recommended in production)
// Use `null` to disable subscriptions
wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql',
// LocalStorage token
tokenName: AUTH_TOKEN,
// Enable Automatic Query persisting with Apollo Engine
persisting: false,
// Use websockets for everything (no HTTP)
// You need to pass a `wsEndpoint` for this to work
websocketsOnly: false,
// Is being rendered on the server?
ssr: false,
// Override default apollo link
// note: don't override httpLink here, specify httpLink options in the
// httpLinkOptions property of defaultOptions.
// link: myLink
// Override default cache
// cache: myCache
// Override the way the Authorization header is set
// getAuth: (tokenName) => ...
// Additional ApolloClient options
// apollo: { ... }
// Client local data (see apollo-link-state)
// clientState: { resolvers: { ... }, defaults: { ... } }
}
// Call this in the Vue app file
export function createProvider (options = {}) {
// Create apollo client
const { apolloClient, wsClient } = createApolloClient({
...defaultOptions,
...options,
})
apolloClient.wsClient = wsClient
// Create vue apollo provider
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$query: {
// fetchPolicy: 'cache-and-network',
},
},
errorHandler (error) {
// eslint-disable-next-line no-console
console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
},
})
return apolloProvider
}
taken from here. All options are shown here.
I have seen in different github discussions that cookies must be placed inside headers, for example here. Then I found, that apollo-link-http has headers option, so at the end I tried different variations of ...:
httpLinkOptions: {
headers: {
// Tried something like:
cookie[s]: 'language=en; path=/;'
// and something like:
cookie[s]: {
language: 'en'
}
}
}
but no luck.
In case of cookieS I receive Error: Network error: Failed to fetch.
In case of cookie, request is sent without issues, but backend does not see language cookie.
I double-checked backend using Postman and in this case backend receives request with manually added language cookie.
Could anyone help me?

Found solution.
FRONT END SETTINGS
Create cookie:
export function languageCookieSet (lang) {
document.cookie = `language=${lang}; path=/;`
}
Add httpLinkOptions to defaultOptions of vue-apollo.js.
const defaultOptions = {
...
httpLinkOptions: {
credentials: 'include'
},
...
BACKEND SETTINGS
As a backend I use Django (currently v2.2.7).
For development we need to use django-cors-headers
My development.py now looks like:
from .production import *
CORS_ORIGIN_WHITELIST = (
'http://localhost:8080',
)
CORS_ALLOW_CREDENTIALS = True
INSTALLED_APPS += ['corsheaders']
MIDDLEWARE.insert(0, 'corsheaders.middleware.CorsMiddleware')
Add to production.py:
LANGUAGE_COOKIE_NAME = 'language'
The default value of LANGUAGE_COOKIE_NAME is django_language, so if it is suitable for you, change
document.cookie = `language=${lang}; path=/;`
to
document.cookie = `django_language=${lang}; path=/;`
Now in backend we can get frontend language:
import graphene
from django.contrib.auth import get_user_model
from django.utils.translation import gettext as _
from .views import user_activation__create_email_confirmation
User = get_user_model()
class UserRegister(graphene.Mutation):
"""
mutation {
userRegister(email: "test#domain.com", password: "TestPass") {
msg
}
}
"""
msg = graphene.String()
class Arguments:
email = graphene.String(required=True)
password = graphene.String(required=True)
def mutate(self, info, email, password):
request = info.context
# Here we get either language from our cookie or from
# header's "Accept-Language" added by Browser (taken
# from its settings)
lang = request.LANGUAGE_CODE
print('lang:', lang)
if User.objects.filter(email=email).exists():
# In our case Django translates this string based
# on the cookie's value (the same as "request.LANGUAGE_CODE")
# Details: https://docs.djangoproject.com/en/2.2/topics/i18n/translation/
msg = _('Email is already taken')
else:
msg = _('Activation link has been sent to your email.')
user = User(email=email)
user.set_password(password)
user.save()
user_activation__create_email_confirmation(info.context, user)
return UserRegister(msg=msg)
Note: I have not tested yet these changes in production, but in production I use just one server where frontend and backend live behind nGinx and this is the reason why CORS settings live in development.py instead of production.py. Also in production credentials: 'include' possibly could be changed to credentials: 'same-origin' (ie more strict).

Related

How to bind a path variable to the request body parameter?

I want to post a HTTP request like this:
http://localhost/context/{{name}}/{{age}}
And I want to bind these path variables to request body, if my request body is :
{
"name": "Frank",
"age": 18
}
the final request I want to send is:
http://localhost/context/Frank/18
so how to achieve this function in POSTMAN?
postman request
Provisioning your request in Postman (non-parametric url):
Parametric url
I don't think you need to pass variables in your route, since you're already passing them in the request-body. However, here's a brief.
If you're working with NodeJS (using Express) and any JS library, you can send the request as thus, (using axios):
const body = {
"name": "Frank",
"age": 18
}
const requestHandler = async() => {
const serverResponse = await axios.post(`http://localhost/context/${body.name}/${body.age}`, {data: body}, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${backend-token}`
}
};
Then, on your server-side, with a proper definition for your routes (for parametric and non-paramatric choice of url), you'd create a route to handle the request as:
Using Express
import {Router} from "express";
const yourServerRouter = Router();
yourServerRouter.post(`yourPrimaryDomain/:name/:age`, function-to-handle-request)
If you're working with a python framework (Flask or Django), you can do thus:
Flask (using flask_restful):
urls = [f"your_base_url/name/age"]
from flask_restful import Resource, request
import json
class BioData(Resource):
def post(self):
url = request.url
if "context" in url:
request_body = json.loads(request.data)
response = self.context_handler(request_data)
def context_handler(self, request_data):
name, age = request_data
....
....
flask_app = Flask(__name__)
flask_app.add_resource(BioData, *urls)
Django (Using DRF - viewsets)
from rest_framework import viewsets, routers
class BioDataViewsets(viewsets.ModelViewSets):
#action(methods=["POST"], detail=False, url_name="context", url_path="context")
def context(self, *args, **kwargs):
clients_request = json.loads(self.request.body)
**define your path as above (for flask)**
context_router = routers.DefaultRouter()
context_router.register("name/age/", company_viewsets.CompanyViewSets)
url_patterns = [path(f"your_base_url/context", include(context_router.urls())]
Eventually I got some clues from this page. The request body can be parsed through Pre-request-Script, and the attributes of interest can be set as variables and referenced in URL.
var r = JSON.parse(request.data);
pm.variables.set("name", r.name);
pm.variables.set("age", r.age);
And use below form to apply variables set in the Pre-request-Script:
http://localhost/context/{{name}}/{{age}}
the request body is :
{
"name": "Frank",
"age": 18
}
postman request

Difference between fetch and postman's results

I have a simple login backend which has two routes login and get_user. After a user is logged in, a cookie is set such that it enables other routes like get_user. I tested this backend with Postman and after correct login, the cookie is set and get_user responds with user's data.
However, when I try to use fetch or axios in React and JS, I get problems. After I fetch login, I can see the cookie is sent, however, fetch get_user acts like cookie is not set at all.
I provided a minimal example to show that server side session somehow doesn't work with fetch:
Frontend:
<!DOCTYPE html>
<html>
<body>
<h1> Set value: </h1> <h2 id="set_value"></h2>
<h1> Get value: </h1> <h2 id="get_value"></h2>
</body>
<script>
// ..\..\Flask_general_framework\backend\venv\Scripts\Activate.ps1
async function Set_user_fetch()
{
// Get user
let user = await fetch('http://127.0.0.1:5000/set/gggg', {
'method': 'GET',
//credentials: 'include',
mode: 'cors',
credentials: "same-origin",
headers: {'Content-type': 'application/json', 'Accept': 'application/json',
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS},
'Access-Control-Allow-Headers': 'Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name',
}
})
user = await user.json();
console.log("await user:", user);
document.getElementById("set_value").innerHTML = user.value;
}
async function Get_user_fetch()
{
let user = await fetch('http://127.0.0.1:5000/get', {
'method': 'GET',
//credentials: 'include',
credentials: "same-origin",
mode: 'cors',
headers: {'Content-type': 'application/json', 'Accept': 'application/json',
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS},
'Access-Control-Allow-Headers': 'Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name',
}
})
user = await user.json();
console.log("await user:", user);
document.getElementById("get_value").innerHTML = user.value;
}
Set_user_fetch().then( () => {
Get_user_fetch();
});
</script>
</html>
Backend:
from re import I
from flask import Flask, session
from flask_session import Session
from flask_cors import CORS
import redis
import datetime as dt
app = Flask(__name__)
CORS(app, supports_credentials=True)
app.config['SECRET_KEY'] = 'super secret key'
#app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_PERMANENT'] = True
#app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:9876')
app.config['PERMANENT_SESSION_LIFETIME'] = dt.timedelta(days=7).total_seconds()
server_session = Session()
server_session.init_app(app)
#app.route('/set/<value>', methods=['GET', 'POST'])
def set_value(value):
session['value'] = value
return {"value": value}
#app.route('/get', methods=['GET', 'POST'])
def get_value():
return {"value": session.get('value', 'None')}
app.run(host='127.0.0.1', port=5000, debug=True)
Server Side
In order to support cross-site cookies in modern browsers, you need to configure your server to use the Set-Cookie attribute SameSite=None (see Flask-specific example here). Unfortunately, this also requires the Secure attribute and an HTTPS enabled server.
For local development, you can get around this by serving your client and server on the same hostname, eg localhost with SameSite=Lax (or omitting SameSite which defaults to "Lax").
By "same hostname" I mean that if your frontend code makes requests to localhost:5000, you should open it in your browser at http://localhost:<frontend-port>. Similarly, if you make requests to 127.0.0.1:5000, you should open it at http://127.0.0.1:<frontend-port>.
Lax same-site restrictions don't come into play if only the ports differ.
Client Side
You have a few problems here...
You're sending headers in your request that do not belong there. Access-Control-Allow-* are response headers that must come from the server.
You set credentials to same-origin but are sending a request to a different host. To use cookies, you need to set credentials to "include". See Request.credentials.
You have no error handling for non-successful requests.
You're also setting a lot of redundant properties and headers and can trim down your code significantly.
async function Set_user_fetch() {
const res = await fetch("http://127.0.0.1:5000/set/gggg", {
credentials: "include",
});
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
const user = await res.json();
console.log("await user:", user);
document.getElementById("set_value").innerHTML = user.value;
}
async function Get_user_fetch() {
const res = await fetch("http://127.0.0.1:5000/get", {
credentials: "include",
});
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
const user = await res.json();
console.log("await user:", user);
document.getElementById("get_value").innerHTML = user.value;
}
If you were using Axios, you would set the withCredentials config to true
axios.get("http://127.0.0.1:5000/set/gggg", {
withCredentials: true
});

Apollo client subscription pass JWT token handled by Django Channels middleware

I use Graphql subscriptions with Apollo client on a Vue3 app using Django graphQL Channels and DjangoGraphqlJWT packages in my backend app.
I'm trying to pass a JWT token on the Apollo subscriptions via the connectionParams.
Following this solution. I implemented a Middleware. However Apollo is passing the connectionParams as a payload. I can't find a way to access the payload at the Middleware level, but only on the consumer.
I could access the query string property from the scope argument in the middleware. However, I can't find a way to pass a query argument after the subscription is initiated.
CLIENT SIDE:
import { setContext } from "apollo-link-context";
import { Storage } from "#capacitor/storage";
import {
ApolloClient,
createHttpLink,
InMemoryCache,
split,
} from "#apollo/client/core";
import { getMainDefinition } from "#apollo/client/utilities";
import { WebSocketLink } from "#apollo/client/link/ws";
const authLink = setContext(async (_: any, { headers }: any) => {
const { value: authStr } = await Storage.get({ key: "auth" });
let token;
if (authStr) {
const auth = JSON.parse(authStr);
token = auth.token;
}
// return the headers to the context so HTTP link can read them
return {
headers: {
...headers,
authorization: token ? `JWT ${token}` : null,
},
};
});
const httpLink = createHttpLink({
uri: process.env.VUE_APP_GRAPHQL_URL || "http://0.0.0.0:8000/graphql",
});
const wsLink = new WebSocketLink({
uri: process.env.VUE_APP_WS_GRAPHQL_URL || "ws://0.0.0.0:8000/ws/graphql/",
options: {
reconnect: true,
connectionParams: async () => {
const { value: authStr } = await Storage.get({ key: "auth" });
let token;
if (authStr) {
const auth = JSON.parse(authStr);
token = auth.token;
console.log(token); // So far so good the token is logged.
return {
token: token,
};
}
return {};
},
},
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
const cache = new InMemoryCache();
export default new ApolloClient({
// #ts-ignore
link: authLink.concat(link),
cache,
});
BACKEND:
asgy.py
from tinga.routing import MyGraphqlWsConsumer
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from tinga.channels_middleware import JwtAuthMiddlewareStack
import os
from django.core.asgi import get_asgi_application
from django.conf.urls import url
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tinga.settings')
application = get_asgi_application()
# import websockets.routing
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": JwtAuthMiddlewareStack(
URLRouter([
url(r"^ws/graphql/$", MyGraphqlWsConsumer.as_asgi()),
])
),
})
channels_middleware.py
#database_sync_to_async
def get_user(email):
try:
user = User.objects.get(email=email)
return user
except User.DoesNotExist:
return AnonymousUser()
class JwtAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
# Close old database connections to prevent usage of timed out connections
close_old_connections()
# Either find a way to get the payload from Apollo in order to get the token.
# OR
# Pass pass the token in query string in apollo when subscription is initiated.
# print(scope) # query_string, headers, etc.
# Get the token
# decoded_data = jwt_decode(payload['token'])
# scope["user"] = await get_user(email=decoded_data['email'])
return await super().__call__(scope, receive, send)
def JwtAuthMiddlewareStack(inner):
return JwtAuthMiddleware(AuthMiddlewareStack(inner))
As far as I understand, I can only access query string / URL params in the Middleware and not the Apollo payload. Would it be possible to pass the token for now in the query string? However since the token might not exist when Apollo client is provided, it needs to be reevaluated like the connectionParams.
Any workaround?
I managed to get the token in the consumer payload and inject the user into the context.
from tinga.schema import schema
import channels_graphql_ws
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser
from graphql_jwt.utils import jwt_decode
from core.models import User
from channels_graphql_ws.scope_as_context import ScopeAsContext
#database_sync_to_async
def get_user(email):
try:
user = User.objects.get(email=email)
return user
except User.DoesNotExist:
return AnonymousUser()
class MyGraphqlWsConsumer(channels_graphql_ws.GraphqlWsConsumer):
"""Channels WebSocket consumer which provides GraphQL API."""
schema = schema
# Uncomment to send keepalive message every 42 seconds.
# send_keepalive_every = 42
# Uncomment to process requests sequentially (useful for tests).
# strict_ordering = True
async def on_connect(self, payload):
"""New client connection handler."""
# You can `raise` from here to reject the connection.
print("New client connected!")
# Create object-like context (like in `Query` or `Mutation`)
# from the dict-like one provided by the Channels.
context = ScopeAsContext(self.scope)
if 'token' in payload:
# Decode the token
decoded_data = jwt_decode(payload['token'])
# Inject the user
context.user = await get_user(email=decoded_data['email'])
else:
context.user = AnonymousUser
And then passing the token in the connectionParams
const wsLink = new WebSocketLink({
uri: process.env.VUE_APP_WS_GRAPHQL_URL || "ws://0.0.0.0:8000/ws/graphql/",
options: {
reconnect: true,
connectionParams: async () => {
const { value: authStr } = await Storage.get({ key: "auth" });
let token;
if (authStr) {
const auth = JSON.parse(authStr);
token = auth.token;
console.log(token); // So far so good the token is logged.
return {
token: token,
};
}
return {};
},
},
});

Django backend configuration to Stripe, React frontend

I have created React frontend for Stripe payment, how can I configure Django Rest Framework server ?
REACT COMPONENT
import React, { Fragment } from "react";
import StripeCheckout from "react-stripe-checkout";
import axios from "axios";
const StripeBtn = () => {
const publishableKey = "pk_test_some key";
const onToken = token => {
const body = {
amount: 999,
token: token
};
axios
.post("http://localhost:8000/payment", body)
.then(response => {
console.log(response);
alert("Payment Success");
})
.catch(error => {
console.log("Payment Error: ", error);
alert("Payment Error");
});
};
return (
<StripeCheckout
label="Go Premium" //Component button text
name="Business LLC" //Modal Header
description="Upgrade to a premium account today."
panelLabel="Go Premium" //Submit button in modal
amount={999} //Amount in cents $9.99
token={onToken}
stripeKey={publishableKey}
image="" //Pop-in header image
billingAddress={false}
/>
);
};
export default StripeBtn;
Regular Django views, instead of this I need Django Rest framework code to receive the axios POST request.
def charge(request): # new
if request.method == 'POST':
charge = stripe.Charge.create(
amount=500,
currency='usd',
description='A Django charge',
source=request.POST['stripeToken']
)
return render(request, 'payments/charge.html')
I have installed Stripe in Django and added key's in settings.py and above view is regular Django function to receive Stripe payments.
I was following a tutorial but they were using express.js as backend
https://medium.com/hackernoon/stripe-api-reactjs-and-express-bc446bf08301

CSRF with Django, React+Redux using Axios

This is an educational project, not for production. I wasn't intending to have user logins as part of this.
Can I make POST calls to Django with a CSRF token without having user logins? Can I do this without using jQuery? I'm out of my depth here, and surely conflating some concepts.
For the JavaScript side, I found this redux-csrf package. I'm not sure how to combine it with my POST action using Axios:
export const addJob = (title, hourly, tax) => {
console.log("Trying to addJob: ", title, hourly, tax)
return (dispatch) => {
dispatch(requestData("addJob"));
return axios({
method: 'post',
url: "/api/jobs",
data: {
"title": title,
"hourly_rate": hourly,
"tax_rate": tax
},
responseType: 'json'
})
.then((response) => {
dispatch(receiveData(response.data, "addJob"));
})
.catch((response) => {
dispatch(receiveError(response.data, "addJob"));
})
}
};
On the Django side, I've read this documentation on CSRF, and this on generally working with class based views.
Here is my view so far:
class JobsHandler(View):
def get(self, request):
with open('./data/jobs.json', 'r') as f:
jobs = json.loads(f.read())
return HttpResponse(json.dumps(jobs))
def post(self, request):
with open('./data/jobs.json', 'r') as f:
jobs = json.loads(f.read())
new_job = request.to_dict()
id = new_job['title']
jobs[id] = new_job
with open('./data/jobs.json', 'w') as f:
f.write(json.dumps(jobs, indent=4, separators=(',', ': ')))
return HttpResponse(json.dumps(jobs[id]))
I tried using the csrf_exempt decorator just to not have to worry about this for now, but that doesn't seem to be how that works.
I've added {% csrf_token %} to my template.
This is my getCookie method (stolen from Django docs):
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
I've read that I need to change the Axios CSRF info:
var axios = require("axios");
var axiosDefaults = require("axios/lib/defaults");
axiosDefaults.xsrfCookieName = "csrftoken"
axiosDefaults.xsrfHeaderName = "X-CSRFToken"
Where do I stick the actual token, the value I get from calling getCookie('csrftoken')?
This Q&A is from 2016, and unsurprisingly I believe things have changed. The answer continues to receive upvotes, so I'm going to add in new information from other answers but leave the original answers as well.
Let me know in the comments which solution works for you.
Option 1. Set the default headers
In the file where you're importing Axios, set the default headers:
import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
Option 2. Add it manually to the Axios call
Let's say you've got the value of the token stored in a variable called csrfToken. Set the headers in your axios call:
// ...
method: 'post',
url: '/api/data',
data: {...},
headers: {"X-CSRFToken": csrfToken},
// ...
Option 3. Setting xsrfHeaderName in the call:
Add this:
// ...
method: 'post',
url: '/api/data',
data: {...},
xsrfHeaderName: "X-CSRFToken",
// ...
Then in your settings.py file, add this line:
CSRF_COOKIE_NAME = "XSRF-TOKEN"
Edit (June 10, 2017): User #yestema says that it works slightly different with Safari[2]
Edit (April 17, 2019): User #GregHolst says that the Safari solution above does not work for him. Instead, he used the above Solution #3 for Safari 12.1 on MacOS Mojave. (from comments)
Edit (February 17, 2019): You might also need to set[3]:
axios.defaults.withCredentials = true
Things I tried that didn't work: 1
I've found out, that axios.defaults.xsrfCookieName = "XCSRF-TOKEN";
and CSRF_COOKIE_NAME = "XCSRF-TOKEN"
DOESN'T WORK IN APPLE Safari on Mac OS
The solution for MAC Safari is easy, just change XCSRF-TOKEN to csrftoken
So, in js-code should be:
import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
In settings.py:
CSRF_COOKIE_NAME = "csrftoken"
This configuration works for me without problems Config axios CSRF django
import axios from 'axios'
/**
* Config global for axios/django
*/
axios.defaults.xsrfHeaderName = "X-CSRFToken"
axios.defaults.xsrfCookieName = 'csrftoken'
export default axios
After spending too many hours researching, and implementing the above answer, I found my error for this problem! I have added this answer to be supplemental of the accepted answer. I had set up everything as mentioned, but the gotcha for me was actually in the browser itself!
If testing locally, make sure you are accessing react through 127.0.0.1 instead of localhost! localhost handles request headers differently and doesn't show the CSRF tokens in the header response, where as 127.0.0.1 will! So instead of localhost:3000 try 127.0.0.1:3000!
Hope this helps.
The "easy way" almost worked for me. This seems to work:
import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";
And in the settings.py file:
CSRF_COOKIE_NAME = "XCSRF-TOKEN"
You could add the Django-provided CSRF token manually into all of your post requests, but that's annoying.
From the Django docs:
While the above method (manually setting CSRF token) can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. This is often easier, because many JavaScript frameworks provide hooks that allow headers to be set on every request.
The docs have code you can use to pull the CSRF token from the CSRF token cookie and then add it to the header of your AJAX request.
There is actually a really easy way to do this.
Add axios.defaults.xsrfHeaderName = "X-CSRFToken"; to your app config and then set CSRF_COOKIE_NAME = "XSRF-TOKEN" in your settings.py file. Works like a charm.
For me, django wasn't listening to the headers that I was sending. I could curl into the api but couldn't access it with axios. Check out the cors-headers package... it might be your new best friend.
I fixed it by installing django-cors-headers
pip install django-cors-headers
And then adding
INSTALLED_APPS = (
...
'corsheaders',
...
)
and
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
into my settings.py
I also had
ALLOWED_HOSTS = ['*']
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
'Access-Control-Allow-Origin: *',
)
in my settings.py although that is probably overkill
In addition to what yestema said (and echoed by krescruz, cran_man, Dave Merwin et. al), You also need:
axios.defaults.withCredentials = true