Search code examples
djangocookiesdjango-rest-frameworkweb-deploymentdjango-csrf

Deploying React front end with Django session based auth doesnt work over HTTPS


So I have a working LOCAL Twitter clone called Hater but cant deploy front end b/c I cant access secured Cookies(https://github.com/mustafabin/hater)

I used Django's built-in Session-based auth I have middleware all set up

LOGIN VIEW

@method_decorator(csrf_protect, name="dispatch")
class LoginView(APIView):
    permission_classes = (permissions.AllowAny,)

    def post(self, request, format=None):
        data = self.request.data

        username = data['username']
        password = data['password']

        try:
            user = auth.authenticate(username=username, password=password)

            if user is not None:
                auth.login(request, user)
                return Response({'success': 'User authenticated'})
            else:
                return Response({'error': 'Error Authenticating'})
        except:
            return Response({'error': 'Something went wrong when logging in'})

SIGN UP

@method_decorator(csrf_protect, name="dispatch")
class SignupView(APIView):
    permission_classes = (permissions.AllowAny,)

    def post(self, request, format=None):
        data = self.request.data

        username = data['username']
        password = data['password']
        re_password = data['re_password']
        tag = data['tag']

        try:
            if password == re_password:
                if User.objects.filter(username=username).exists():
                    return Response({"error": "Username already exists"})
                else:
                    if len(password) < 6:
                        return Response({"error": "Password must be at least 6 characters"})
                    else:
                        user = User.objects.create_user(
                            username=username, password=password)

                        user = User.objects.get(id=user.id)

                        user_profile = User_profile.objects.create(
                            user=user, name=username, tag=tag)

                        return Response({'success': "User created successfully"})
            else:
                return Response({'error': "Passwords do not match"})
        except:
            return Response({"error": "Something went wrong signing up"})

I'm aware some of these settings are redundant but ur man got desperate

CORS_ORIGIN_ALLOW_ALL = True
CSRF_COOKIE_HTTPONLY = False
SESSION_COOKIE_HTTPONLY = False
CORS_ALLOW_CREDENTIALS = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SAMESITE = 'None'
CSRF_TRUSTED_ORIGINS = ['http://localhost:3000', 'http://localhost:8000',
                        'https://hater.netlify.app', 'https://haterip.netlify.app']
CORS_EXPOSE_HEADERS = ["Set-Cookie"]
django_heroku.settings(locals())

and this is React.js code that handles login and the terinary that displays the login form when user isnt logged in user.tag is a global state that is null if no current user is logged in

let handleLogin = (e) => {
    e.preventDefault();
    let headerInfo = {
      Accept: "application/json",
      "Content-Type": "application/json",
    };
    let loginOptions = {
      method: "POST",
      headers: headerInfo,
      credentials: "include",
      body: JSON.stringify(form),
    };
    let options = {
      method: "GET",
      headers: headerInfo,
      credentials: "include",
    };

    fetch(`https://haterbackend.herokuapp.com/user/login`, loginOptions)
      .then((res) => res.json())
      .then((data) => {
        if (data["error"]) {
          return alert(data["error"]);
        } else {
          fetch(`https://haterbackend.herokuapp.com/user/grabProfile`, options)
            .then((res) => res.json())
            .then((data) => {
              store.dispatch({ type: "set", payload: data.profile });
            })
            .then(() => navigate("/home"))
            .catch((err) => console.log(err));
        }
      })
      .catch((err) => console.log(err));
  };
  {!user.tag ? (
      <form onSubmit={handleLogin} className="landingForm">
        <CSRFToken></CSRFToken>
        <input
          onChange={handleChange}
          className="landingLoginInput"
          placeholder="Username"
          type="text"
          name="username"
        />

        <input
          onChange={handleChange}
          className="landingLoginInput"
          placeholder="Password"
          type="password"
          name="password"
          autoComplete="current-password"
        />
        <Button id="login" type="submit">
          Login
        </Button>
      </form>
    ) : (
      <div className="landing-signout">
        <Link className="landing-home-link" to="/home">
          <Button>Home 🏡</Button>
        </Link>
        <Link className="landing-signout-link" onClick={signOut} to="/">
          <Button>Sign out 🚪</Button>
        </Link>
      </div>
    )}

The CSRFToken componet is just a hidden input field

import React, { useState, useEffect } from "react";

export default function CSRFToken() {
  const [csrftoken, setcsrftoken] = useState("");

  const getCookie = (name) => {
    let cookieValue = null;
    if (document.cookie && document.cookie !== "") {
      let cookies = document.cookie.split(";");
      for (let i = 0; i < cookies.length; i++) {
        let cookie = cookies[i].trim();
        if (cookie.substring(0, name.length + 1) === name + "=") {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  };

  useEffect(() => {
    fetch(`https://haterbackend.herokuapp.com/user/csrf_cookie`, {
      credentials: "include",
    })
      .then((res) => {
        setcsrftoken(getCookie("csrftoken"));
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

  return (
    <input type="hidden" name="csrfmiddlewaretoken" value={csrftoken || ""} />
  );
}

I implemented session login with react front end as outlined here: https://www.stackhawk.com/blog/django-csrf-protection-guide/ https://docs.djangoproject.com/en/3.1/ref/csrf/#ajax official doc

the method above only worked locally on HTTP but wouldnt work over HTTPS because the deployed site wouldnt set the cookie because it wasnt secured error screenshot

But client side scripts cant grab secure cookies and the getCookie function from the Django docs only parses thru a set cookie header so it wouldnt work if the cookie is undefined or empty ( client side script code attempts to read the cookie, the browser returns an empty string as the result source linked below) https://owasp.org/www-community/HttpOnly

TLDR: Project works locally HTTP but when deployed cookies cant be set over HTTPS but client-side scripts cant read secure cookies so i cant register or log users in because that requires the csrftoken cookie


Solution

  • Super late reply but its not possible to securely use session based auth when my backend service was deployed on a different domain.

    front end was on netifly back end was on heroku

    a better alternative that I used was django knox tokens it behaves the same way as JWT authentication but has more functionality and tokens can be invalidated.

    TLDR; because the services were not under the same domain its not possible under a https connection