Miserlou / Zappa

Serverless Python
https://blog.zappa.io/
MIT License
11.89k stars 1.2k forks source link

Django project deployed with zappa: Forbidden (403) CSRF verification failed #2101

Open PietarTheWise opened 4 years ago

PietarTheWise commented 4 years ago

I've posted similar post here last week regarding a different error in the same area of my website. I've been working on my first website with django, it's deployed to aws with zappa. The page is using Lambda, S3, Apigateway and cloudfront. I've had issues with the links, for some reason the {%url 'contact'%} link for example changes the url to: https://mywebsite.com/website_project_1/contact when it should be https:/mywebsite.com/contact. In cloudfront I have put the origin path to website_project_1 'cause otherwise the whole site would be forbidden. Which has forced me to put the basebath in zappa_settings.json to website_project_1. When I get the api url from my terminal after deployment/update the form works fine. But when I try to send the form from my actual website urls I've set up in aws the website throws the following error:


Forbidden (403)

CSRF verification failed. Request aborted.

Reason given for failure:

    Referer checking failed - https://mywebsite/contact does not match any trusted origins.

My settings.py looks like this:


import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SECRET_KEY = os.environ['SECRET_KEY']

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ['DEBUG']

# EMAIL_HOST =

ALLOWED_HOSTS = ['127.0.0.1', '(my_api_numbers).execute-api.eu-west-1.amazonaws.com', 'mywebsite.co/']

# Application definition

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

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

ROOT_URLCONF = 'my_website.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'website.wsgi.application'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME':
        'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME':
        'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME':
        'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME':
        'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATIC_URL = '/static/'

EMAIL_BACKEND = 'django_ses.SESBackend'

AWS_ACCESS_KEY_ID = 'aws_accesskey'
AWS_SECRET_ACCESS_KEY = 'aws_secretkey'

AWS_SES_REGION_NAME = 'eu-west-1'
AWS_SES_REGION_ENDPOINT = 'email.eu-west-1.amazonaws.com'

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

AWS_STORAGE_BUCKET_NAME = 'my_bucket'

AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}

AWS_LOCATION = 'static'
AWS_QUERYSTRING_AUTH = False

STATIC_URL = "https://s3.amazonaws.com/%s/static/" % AWS_STORAGE_BUCKET_NAME

AWS_DEFAULT_ACL = None 

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] 

STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS

CSRF_COOKIE_DOMAIN = 'mywebsite.co/'

and here's my views file:

´´´

def contact(request):

if request.method == 'POST':
    message_name = request.POST['name']
    message_email = request.POST['email']
    message_organization = request.POST['organization']
    message_title = request.POST['title']
    message_message = request.POST['message']
    message_field = request.POST['subject']
    message_consent = request.POST['consent']

    message_info = "Name: {} \n Email: {} \n Organization: {} \n Field: {} \n Consent: {} \n \n Message: \n {}".format(message_name, message_email, message_organization, message_field, message_consent, message_message)

    send_mail(
        message_title,
        message_info,
        'forms@company.co',  # from email
        ['sales@company.co'],  # To email
    )

    return render(request, 'pages/contact.html', {'name': message_name})

else:
    return render(request, 'pages/contact.html', {})

zappa_settings.json

{
  "website_project_1": {
    "aws_region": "eu-west-1",
    "django_settings": "my_website.settings",
    "profile_name": "default",
    "project_name": "my-website",
    "runtime": "python3.8",
    "s3_bucket": "my_bucket",
    "base_path": "website_project_1"
  }
}

And the form:

<form action="{% url 'contact'%}" method="post" id="contact_form">
      {% csrf_token %}
      <div class="name">
        <label for="name"></label>
        <input
          type="text"
          placeholder="Name"
          name="name"
          id="name_input"
          required
        />
      </div>
      <div class="email">
        <label for="email"></label>
        <input
          type="email"
          placeholder="E-mail"
          name="email"
          id="email_input"
          required
        />
      </div>
      <div class="organisation">
        <label for="name"></label>
        <input
          type="text"
          placeholder="Organization"
          name="organization"
          id="organisation_input"
          required
        />
      </div>

      <div class="title">
        <label for="name"></label>
        <input
          type="text"
          placeholder="Title"
          name="title"
          id="title_input"
          required
        />
      </div>
      <div class="message">
        <label for="message"></label>
        <textarea
          name="message"
          placeholder="message"
          id="message_input"
          cols="30"
          rows="5"
          required
        ></textarea>
      </div>

      <div class="subject">
        <label for="subject"></label>
        <select
          placeholder="Subject line"
          name="subject"
          id="subject_input"
          required
        >
          <option value="">Options</option>
          <option value="option 1">A</option>
          <option value="Option 2"
            >B</option
          >
          <option value="option3">C</option>
          <option value="option4">D</option>
          <option value="option5">Other</option>
        </select>
      </div>

      <div class="consent">
        <input type="checkbox" id="" name="consent" value="yes" required />
        <label for="consent"
          >I consent to my personal data being stored by company ltd(we
          do not sell or otherwise release your personal data to third parties
          or use the information for marketing purposes)</label
        >
      </div>

      <div class="submit">
        <input type="submit" value="Send Message" id="form_button" />
      </div>

I've tried plenty of configuring in the aws end but I might be missing out something there ...or in the code. Any help is greatly appreciated!

auslaner commented 4 years ago

I think this is related to how cloudfront forwards headers. Does this only happen when you use the custom domain name or cloudfront url or does it also happen when you use the api gateway url directly?