UPDATE: I’m simplifying this as it got too long
I am trying to get SessionAuthentication to work with React (port 3000) and Django (port 8000). However, I am having trouble with csrftoken not being set in React that causes the FORBIDDEN request to api/user.
My example code for this are all here:
https://github.com/axilaris/docker-django-react-celery-redis
You can try it, very simply run these. And go through these simple steps: register, login, refresh the page:
Django (runs on port 8000)
python3 -m venv backendvirtualenv && source backendvirtualenv/bin/activate
pip install -r requirements.txt
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
React (runs on port 3000)
npm install
npm run build
npm start
Django side (settings.py):
CORS_ALLOWED_ORIGINS = [
'http://localhost',
'http://127.0.0.1',
'http://0.0.0.0',
'http://127.0.0.1:3000',
]
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
INSTALLED_APPS = [
...
'corsheaders',
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
}
SESSION_COOKIE_HTTPONLY = True # Default value is True, which is recommended
SESSION_COOKIE_SAMESITE = 'Lax' # Consider 'None' if strictly necessary and secure is set
SESSION_COOKIE_SECURE = False # Non-Production Port 80
CSRF_COOKIE_HTTPONLY = False # Should generally be False to allow JavaScript to read the value
CSRF_COOKIE_SECURE = False # Non-Production Port 80
Django (views.py)
class UserLogin(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
##
def post(self, request):
...
if serializer.is_valid(raise_exception=True):
user = serializer.check_user(data) <-- authenticate is in here
login(request, user)
return Response(serializer.data, status=status.HTTP_200_OK)
class UserView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
##
def get(self, request):
..
serializer = UserSerializer(request.user)
return Response({'user': serializer.data}, status=status.HTTP_200_OK)
React (App.js):
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
axios.defaults.withCredentials = true;
const client = axios.create({
baseURL: "http://127.0.0.1:8000"
});
....
function submitLogin(e) {
e.preventDefault();
client.post(
"/api/login",
{
email: email,
password: password
}
).then(function(res) {
setTimeout(() => {
const csrftoken = Cookies.get('csrftoken');
const sessionid = Cookies.get('sessionid');
console.log("XXX /api/login csrftoken:" + csrftoken);
console.log("XXX /api/login sessionid:" + sessionid);
console.log('cookies', document.cookie)
}, 100);
setCurrentUser(true);
});
Network logs (to proof csrftoken exist in api/login and forbidden in api/user):
https://gist.github.com/axilaris/f10f6eface04bd4f94a427bb347c10c3
Logs screenshots: https://imgur.com/a/juO7Z6h
Troubleshooting Analysis:
notice in the logs, that csrftoken is not printed out
App.js:103 XXX /api/login csrftoken:undefined
App.js:104 XXX /api/login sessionid:undefined
App.js:106 cookies <--- this is totally empty from console.log('cookies', document.cookie)
Network logs from api/login, csrftoken does exist! but why it doesnt get into React axios.
Set-Cookie:
csrftoken=yO8XukEnOSWYgwYxae4nd7c2OEHF2UQG; expires=Mon, 10 Mar 2025 22:26:02 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Set-Cookie:
sessionid=rbj2ewpg11sx0t7q9j16ika01nk46074; expires=Mon, 25 Mar 2024 22:26:02 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
Vary:
- I’ved even updated axios version to the latest 1.6.7
- I tried both Chrome and MS Edge browser
2
Answers
You have:
For
SessionAuthentication
to work, make sure Django’s session cookie (sessionid
) and CSRF token cookie (csrftoken
) are correctly set and sent to the client. TheHttpOnly
flag should generally be set on thesessionid
cookie to enhance security, as mentioned in this thread.Since the session cookie (
sessionid
) should beHttpOnly
, the React application will not directly interact with it. Instead, make sure theaxios
configuration correctly includes credentials with every request. That part seems to be configured correctly in your setup (axios.defaults.withCredentials
set totrue
).settings.py
should include:For CSRF cookie configuration:
Setting
CSRF_COOKIE_HTTPONLY
toFalse
would make your application slightly more vulnerable to XSS attacks, as it allows JavaScript to access the CSRF cookie. A better approach would be to keep the CSRF tokenHttpOnly
, and obtain it from a separate API endpoint designed to return the CSRF token explicitly, or include the CSRF token within the body of your page and have your JavaScript read it from there.Although… as documented since 2016 in
django/django
PR 7700 and commit c27104aThe primary risk to consider when making CSRF tokens accessible to JavaScript is XSS — Cross site scripting . If your application is vulnerable to XSS, an attacker could exploit this to bypass CSRF protection entirely. Therefore, any decision to expose CSRF tokens to JavaScript should be accompanied by rigorous XSS mitigation strategies. See, for instance, "Django XSS: Examples and Prevention" from StackHawk.
Test the flow of obtaining a CSRF token and session cookie upon login. Verify that subsequent requests from React include the CSRF token in the header and the session cookie automatically (even though the JavaScript cannot read the
sessionid
cookie due to theHttpOnly
flag).Given that the React frontend and Django backend are likely running on different ports (even if they are on the same localhost), proper CORS configuration is key: you have mentioned that
CORS_ALLOW_CREDENTIALS
is set toTrue
, which is necessary for requests that include credentials like cookies and authorization headers. However, making sure that the frontend’s origin is explicitly allowed in the Django backend’s CORS settings is also important.So check
CORS_ALLOWED_ORIGINS
includes the React application’s origin (e.g.,http://localhost:3000
if React runs on port 3000).You have set
CSRF_COOKIE_HTTPONLY
toFalse
andCSRF_COOKIE_SECURE
set toFalse
because you are using port 80 and not HTTPS, which is understandable for development. But the CSRF token has also to be properly included in requests after a page reload: the React application needs to send the CSRF token with state-changing requests (e.g., POST, PUT, DELETE) in the headers.If your Django backend sends the CSRF token as a cookie (
csrftoken
), your application needs to read this cookie and include the token in the headers of subsequent requests. SinceCSRF_COOKIE_HTTPONLY
is set toFalse
, you can use JavaScript to access the cookie value.When making requests to your backend, include the CSRF token in the request headers. With Axios, you can set the headers for each request or globally. If you are using cookies and
axios
, and assuming you have access to thejs-cookie
library or similar for cookie handling, the setup might look like this:Then, check that, after reloading the page, the React application still has access to the latest CSRF token and includes it in the headers of requests to
/api/user
.The session cookie settings seem to be correctly configured for development (
SESSION_COOKIE_HTTPONLY = True
andSESSION_COOKIE_SECURE = False
). Do see if the browser is sending the session cookie back to the server after a page reload. TheHttpOnly
flag prevents JavaScript from accessing the cookie, but the browser should automatically handle it and include it in subsequent requests to the backend.Check also the request headers in the browser’s developer tools to confirm that the
sessionid
cookie is being sent with the request to/api/user
.Regarding the Django View serving
/api/user
, see if it is correctly configured to handle session authentication and that there are no additional permission checks failing. If the view uses Django REST Framework, verify thatpermissions.IsAuthenticated
is set and functioning as expected.You can use Django’s logging to output session and authentication information in the view serving
/api/user
to make sure the request is correctly authenticated. Confirm there is no mismatch in the session due to any misconfiguration that could lead to a new session being created after a reload, invalidating the previous session.The CSRF token obtained from the cookie (
1qJvVnbBRdkPgGBYd8KLK7wDg7KOE2QU
) does not match the token in the network logs for the login response (rsyLvHLLNAIumReGxLa63PONPM3klYIq
).Django generates a unique CSRF token per session or even per form/request in certain circumstances. That means the token can change, especially after login, logout, or other session state changes. The mismatch you are observing suggests that after login, a new CSRF token is issued, but the client-side application continues to use the old token.
The issue will come if your client-side code does not read the new token from the cookie after the session state changes.
To address this, make sure your React application updates the CSRF token it uses for subsequent requests anytime the token might have changed, especially after login, logout, or registration actions: make sure to fetch the new CSRF token from the cookie and update the Axios default headers or the headers of any subsequent request with this new token.
Also, modify your
useEffect
hook to listen for changes in user authentication state and update the CSRF token accordingly. That might involve restructuring how you manage and track authentication state in your application.After implementing the token update mechanism, log the CSRF token before making a request to make sure it matches the latest token expected by the server. Use browser developer tools to inspect the
Set-Cookie
headers in the response of your login/register requests to confirm a new CSRF token is issued and make sure subsequent requests are using this new token in theirX-CSRFToken
header.I think in your latest comment, that is precisely what I have done, if you looked into
App.js
, I have tried to updatecsrftoken
fromgetCookie
to Axios toapi/register
,api/login
,api/user
. Is there a possibility we can read out the correctSet-Cookie
csrftoken?True: your code attempts to update the CSRF token from the cookies for each request. But make sure this update happens after the cookies have been updated by the server response. That might not be fully effective if the cookie reading and Axios header update occurs before the browser has processed and saved the new
csrftoken
cookie.After operations that are likely to change the CSRF token (like login or register), explicitly fetch the CSRF token from the cookie again and update the Axios headers. That might seem redundant with the current setup, but making sure this happens as a callback or a subsequent step after the server’s response might address timing issues.
Then, as mentioned before, use the browser’s developer tools to inspect the cookies immediately after login and other state-changing operations. Make sure the
csrftoken
cookie is updated in the browser before your application attempts to read it again.When you configure an Axios instance with defaults (like headers), these defaults are fixed at the instance creation time. If a CSRF token changes (which happens during login, logout, or session expiration), the original Axios instance still retains the old token in its defaults: the instance does not automatically update its defaults based on changes in browser cookies or external changes.
If a CSRF token is updated on the server side and sent to the client via a
Set-Cookie
header, updating the cookie in the browser does not automatically update the Axios default headers configured previously. Sinceaxios.defaults
or an Axios instance’s defaults are not dynamically linked to browser cookies, they do not reflect updates unless explicitly set again.But: creating a new Axios instance for each request, or after key actions like login, would make sure the most current CSRF token from the cookies is used to configure the instance. That approach effectively isolates the state (headers, in this case) of each request, making sure that changes like CSRF token updates are accounted for.
By fetching the CSRF token from the cookies right before creating a new Axios instance, you make sure the instance uses the most up-to-date token. That dynamic configuration process addresses the issue where the original, statically configured instance would not reflect changes in CSRF token values.
While creating a new Axios instance dynamically can solve the immediate problem of stale CSRF tokens, it can also introduce unnecessary overhead and complexity, especially in applications making frequent API calls.And it can lead to code duplication and make managing HTTP configurations more cumbersome.
Instead of creating a new Axios instance for each request, consider implementing a centralized mechanism to manage and update CSRF tokens. For example, you could use Axios interceptors to dynamically set the
X-CSRFToken
header before each request based on the current value of the CSRF token cookie.Use Axios request interceptors to read the CSRF token from the cookies and set it in the request headers dynamically. That makes sure each request uses the latest token without needing to create new Axios instances.
hello try this,
Open the
setupProxy.js
file and add the following code:other django settings seem ok to me, so dont touch them for now.
option 2
in
package.json
file, add this attribute at firsst indent level."proxy":"http://localhost:8000"
google about how cookies are set, you will understand the secure context concept, and why different origins are security issue when setting cookies. explanation not provide for brevity. also make sure, use
/api/...
and relative to it for further requests for the same reason.personal opinion: django is a good man, react is magician here. the defaults of react are quite tricky to understand, and i missed deadlines, so i left react for sveltkit.