How to handle secure http (https) via wsgi

October 28, 2008 – 2:53 am

Just a little note… If you happen to run django + wsgi + apache and want to access your site via https, you will definitely need this.

Problem: A site, deployed on client’s server redirects all https requests to http.

Initially we’ve thought that a misconfigured apache is to be blamed.

It appeared that not.

Looks like that django wsgi handler under some configurations can’t decide if incoming connection is secure. You have to pass that info explicitly (e.g. via a header) and modify your wsgi handler.

We’ve based this code snippet on a fairview computing post:

import logging

from django.conf import settings
import django.core.handlers.wsgi

class WSGIRequest(django.core.handlers.wsgi.WSGIRequest):
    """
    WSGIRequest subclass for use behind a proxy that handles SSL. 
It checks for a header indicating whether a request should be
considered secure. By default, it looks for 'X-Forwarded-Proto'
(which will appear in request.META as 'HTTP_X_FORWARDED_PROTO')
and expects a value of 'http' or 'https'. All string checks are
case-insensitive. You can configure its behavior with these
settings:

FC_WSGI_PROTOCOL_HEADER: the name of the header to check.
FC_WSGI_PROTOCOL_HTTPS_VALUE: the value to expect on HTTPS requests
FC_WSGI_PROTOCOL_HTTP_VALUE: the value to expect on HTTP requests

You should make sure that the front-end proxy scrubs the header;
it should not be possible for a client to send the header on a
plaintext connection and have it reach the back-end server.

To use this, simply replace the stock Django WSGIHandler in your
WSGI script. Here's a complete example:

import os
import sys

sys.path.append(”/usr/local/django/apps”)

sys.path.append(”/usr/local/django/sites”)

os.environ['DJANGOSETTINGSMODULE'] = ‘yourapp.settings’

os.umask(0007)

_application = django.core.handlers.wsgi.WSGIHandler()

def application(environ, start_response):
    environ['PATH_INFO'] = environ['SCRIPT_NAME'] + environ['PATH_INFO']
    if environ.get('HTTPS','off') in ('on','1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'
    if environ['wsgi.url_scheme'] == 'https':
            environ['HTTPS'] = 'on'

    return _application(environ, start_response)

"""

def is_secure(self):
    logger = logging.getLogger('fairview.wsgi.WSGIRequest.is_secure')
    header = getattr(settings,
                        'FC_WSGI_PROTOCOL_HEADER',
                        'HTTP_X_FORWARDED_PROTO'
                        )

    https_value = getattr(settings,
                            'FC_WSGI_PROTOCOL_HTTPS_VALUE',
                            'https'
                            ).lower()

    http_value = getattr(settings,
                            'FC_WSGI_PROTOCOL_HTTP_VALUE',
                            'http'
                            ).lower()
    value = self.META.get(header, '').lower()

    if settings.DEBUG:
        logger.debug("""HEADER: '%s' HTTPS value: '%s' HTTP value: '%s' request value: '%s'""" % (header, https_value, http_value, value))
    if value == https_value:
        if settings.DEBUG:
            logger.debug("""Request is secure.""")
        return True

    if settings.DEBUG:
        logger.debug("""Request is insecure.""")
    return False

class WSGIHandler(django.core.handlers.wsgi.WSGIHandler):
request_class = WSGIRequest

_application = WSGIHandler()

def application(environ, startresponse):
environ['PATH
INFO'] = environ['SCRIPTNAME'] + environ['PATHINFO']
if environ.get(’HTTPS’,'off’) in (’on’,'1′):
environ['wsgi.urlscheme'] = ‘https’
else:
environ['wsgi.url
scheme'] = ‘http’
if environ['wsgi.url_scheme'] == ‘https’:
environ['HTTPS'] = ‘on’

return _application(environ, start_response)

blog comments powered by Disqus