skip to Main Content

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


  1. You have:

    Client (React) --> [HTTP Request with CSRF Token] --> Django Backend
                                                      /
                                                      |
                                                      +--- [Session Authentication & CSRF Validation]
    

    For SessionAuthentication to work, make sure Django’s session cookie (sessionid) and CSRF token cookie (csrftoken) are correctly set and sent to the client. The HttpOnly flag should generally be set on the sessionid cookie to enhance security, as mentioned in this thread.

    Since the session cookie (sessionid) should be HttpOnly, the React application will not directly interact with it. Instead, make sure the axios configuration correctly includes credentials with every request. That part seems to be configured correctly in your setup (axios.defaults.withCredentials set to true).

    settings.py should include:

    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 = True    # Set to True if you are using HTTPS
    

    For CSRF cookie configuration:

    CSRF_COOKIE_HTTPONLY = False    # Should generally be False to allow JavaScript to read the value
    CSRF_COOKIE_SECURE = True       # Set to True if you are using HTTPS
    

    Setting CSRF_COOKIE_HTTPONLY to False 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 token HttpOnly, 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 c27104a

    Designating the CSRF cookie as HttpOnly doesn’t offer any practical protection because CSRF is only to protect against cross-domain attacks. If an attacker can read the cookie via JavaScript, they’re already on the same domain as far as the browser knows, so they can do anything they like anyway. (XSS is a much bigger hole than CSRF.)

    The 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 the HttpOnly flag).


    I’m still getting Forbidden: /api/user (after register/login, and then I reload the page).
    You can find my settings.py and the logs here: https://gist.github.com/axilaris/f0f41dc843c05d0f4cfd40cfefb3478e.

    I do undertand generally the flow how front end should ensure withCredentials = True, and I think CORS_ALLOW_CREDENTIALS = True.
    The rest I’m not to sure.

    I am using port 80 in Docker, so I have disabled SECURE for CSRF_COOKIE_SECURE = False and SESSION_COOKIE_SECURE = False. What is the problem why is it still Forbidden ?

    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 to True, 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 checkCORS_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 to False and CSRF_COOKIE_SECURE set to False 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. Since CSRF_COOKIE_HTTPONLY is set to False, 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 the js-cookie library or similar for cookie handling, the setup might look like this:

    import axios from 'axios';
    import Cookies from 'js-cookie';
    
    // Retrieve the CSRF token from the cookie
    const csrfToken = Cookies.get('csrftoken');
    
    // Set the CSRF token in the Axios default headers
    axios.defaults.headers.common['X-CSRFToken'] = csrfToken;
    

    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 and SESSION_COOKIE_SECURE = False). Do see if the browser is sending the session cookie back to the server after a page reload. The HttpOnly 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 that permissions.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.


    I have noticed something, the csrftoken from getCookie is 1qJvVnbBRdkPgGBYd8KLK7wDg7KOE2QU, but in network logs for login response is rsyLvHLLNAIumReGxLa63PONPM3klYIq.
    I have even tried to clear the browser cache. I tried both Chrome and MS Edge Browser to verify.

    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.

    // Example function to update CSRF token in Axios headers
    function updateCSRFToken() {
        const csrfToken = Cookies.get('csrftoken');
        axios.defaults.headers.common['X-CSRFToken'] = csrfToken;
    }
    
    // Call this function after login, registration, or logout
    updateCSRFToken();
    

    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 their X-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 update csrftoken from getCookie to Axios to api/register, api/login, api/user. Is there a possibility we can read out the correct Set-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.

    // Example: Updating CSRF token explicitly after login
    client.post("/api/login", { email, password }).then((response) => {
      // That ensures we read the CSRF token after the response is processed
      const csrfToken = Cookies.get('csrftoken');
      axios.defaults.headers.common['X-CSRFToken'] = csrfToken;
      console.log("Updated CSRF Token after login:", csrfToken);
      setCurrentUser(true);
      // Additional logic here if needed
    });
    

    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.


    I did a new trick that WORKS. I created a new axios instance on api/login and on the api/user.
    The surprise is that api/login generates a new csrftoken just for the first time in the console print. And then, api/user works.
    You can check the App.js code that uses a new axiosInstance.

    Could you explain why the original axios.create() that is can be reusable everywhere does not work?
    I don’t like this new method I have developed to create a new connection every time.

    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. Since axios.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.

    axios.interceptors.request.use(function (config) {
        // Retrieve and set the CSRF token here
        config.headers['X-CSRFToken'] = Cookies.get('csrftoken');
        return config;
    });
    
    Login or Signup to reply.
  2. hello try this,

    npm install http-proxy-middleware
    

    Open the setupProxy.js file and add the following code:

    const { createProxyMiddleware } = require('http-proxy-middleware');
    
    module.exports = function(app) {
      app.use(
        '/api',
        createProxyMiddleware({
          target: 'http://localhost:8000',
          changeOrigin: true,
        })
      );
    };
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search