Search code examples
djangovue.jsaxioscsrf

Vue Django app Forbidden (CSRF cookie not set.): 403 Forbidden


I suppose this would actually be a django question as I think there's something wrong with the backend. I have my Vue code in frontend/ (127.0.0.1:8080) and django code in backend/ (127.0.0.1:8000). I've followed the django docs on CORS and CSRF Tokens but I get Forbidden (CSRF cookie not set.): when trying to make a post request to the django server. I'm trying to reset password via email.

backend/settings.py

...
CORS_ALLOW_ALL_ORIGINS = True # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect
CORS_ALLOW_CREDENTIALS = True

CSRF_TRUSTED_ORIGINS = [
    'http://127.0.0.1:8080',
]

CORS_ALLOWED_ORIGINS = [
    'http://127.0.0.1:8080',
]

# SESSION_COOKIE_SAMESITE = 'None'
# CORS_ALLOW_CREDENTIALS = True

EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_DIR = BASE_DIR / 'emails'



# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',
    'rest_framework.authtoken',
    'corsheaders',
    'djoser',

    'product',
    'order',
    'email_app'
]



MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...

backend/urls.py:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('djoser.urls')),
    path('api/v1/', include('djoser.urls.authtoken')),
    path('api/v1/', include('product.urls')),
    path('api/v1/', include('order.urls')),
    path('api/v1/', include('email_app.urls'))
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

backend/email_app/urls.py:

from django.urls import path

from email_app import views

urlpatterns = [
    path('reset_password/', views.reset_password),
    
]

backend/email_app/views.py:

from rest_framework.decorators import api_view
from django.core.mail import send_mail
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from django.template.loader import render_to_string
from rest_framework.response import Response
from rest_framework import status
from django.middleware.csrf import get_token

from django.views.decorators.csrf import csrf_protect
from rest_framework import status
from rest_framework.decorators import api_view

from .serializers import ResetPasswordEmail

@api_view(['POST', 'GET'])
@csrf_protect
def reset_password(request):
    if request.method == "POST":
        serializer = ResetPasswordEmail(data=request.data)
        if serializer.is_valid():

            # get the email from Front-End
            email = serializer.validated_data['email']

            # find the user that has that email
            user = User.objects.get(email=email)

            # get the token of that user
            token = Token.objects.get(user=user)

            if user:

                # pass the context of things above to send them in an email
                context = {
                    'email': email,
                    'username': user,
                    'token': token
                }

                send_mail(
                    'd-commerce Password Reset',
                    render_to_string('emails/reset_password.txt', context),
                    'D-COMMERCE and No Reply',
                    [email],
                    fail_silently=False,
                    auth_user=None, auth_password=None, connection=None, html_message=None
                )
                serializer.save(token=token, slug=token)
                

            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
...

For vue I use:

...
// using jQuery
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 = jQuery.trim(cookies[i]);
         // 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;
}
...

and using axios to send the request:

...
const csrftoken = this.getCookie('csrftoken');
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.headers.common['X-CSRFToken'] = csrftoken;
axios.post("/api/v1/reset_password/", formData)
     .then(response => {
        toast({message: 'If you have an account with us, 
               please check your email for the link!',
               type: 'is-success',
               dismissible: true,
               pauseOnHover: true,
               position: 'bottom-right',})
     })
...

Solution

  • Ok so this seems to be the fix, I added withCredentials = true and set the headers like this:

    axios.defaults.withCredentials = true;
    axios.defaults.headers.common = {
        'X-Requested-With': 'XMLHttpRequest',
        'X-CSRFToken' : this.getCookie('csrftoken')
    };