How to build a Django site using a subdomain and make it work along with the main domain

How to build a Django site using a subdomain and make it work along with the main domain

In this article, we want to explain the best way to use subdomains and how to configure a Django project to work on a subdomain. We will use as an example a store on the main domain with a user account area on the subdomain in the Django project.

What are a subdomain and what benefits of using it?

When you visit a website, you may notice that the URL in the search bar changes slightly based on your location or which web page you're viewing. It happens when the site is built with subdomains to separate and organize content for a specific function.

A subdomain is a prefix added to a domain name to separate a section of your website. Site owners primarily use subdomains to manage extensive sections that require their content hierarchy, such as online stores, blogs, or support platforms. For example, the domain looks like this: https://example.com/. And your subdomain will be something like: https://prefix.example.com/.

In this article, we will separate the user account and all functionality for it on the subdomain from the main online store domain. It will help us to separate users and product functionality.

How to create a subdomain for a website

The first thing we'll need to do is to find instructions on our hosting site panel on how to add subdomains to our domain. Mostly we can configure it in our account configurations in the “Domain” section. Then we should find something like “subdomains” or “NS records” (nameserver records). In this section we should configure our subdomain details: choose a name, type of DNS record, etc. We will not go deep into details what are the differences, you might want to ask your hosting provider or DevOps to help with it.

How to configure subdomains in the Django project

Then when we have a host, we should configure our project. When we have a domain and subdomain and If we don't want to share cookies or sessions between a domain and a subdomain, then our settings will be pretty simple for the project. More problems arise when a session, cookie, and storage data need to be exchanged between them.

So lets configure the same website to serve data between the domain and subdomain using Django project settings.

The first thing you should remember is that when you want to share authentication settings between domain and subdomain, you can use only session authentification and you can not use any token build authentication (like Knox token authentication), because it could not be exchanged between them correctly.

So let's configure Django setting file for our domain https://example.com/ and subdomain https://prefix.example.com/

Change your settings.py file to have next settings:

# Django settings

# All our necessary imports and other settings

DOMAIN_NAME = 'example.com'

ALLOWED_HOSTS = [
    '.example.com',
]
# Or you can use full list of you subdomains to avoid any unrestricted access
ALLOWED_HOSTS = [
    'example.com',
    'prefix.example.com',
]

CORS_ALLOWED_ORIGINS = [
    "https://example.com",
    "https://prefix.example.com",
]

CORS_EXPOSE_HEADERS = ['Content-Type', 'X-CSRFToken']
CORS_ALLOW_CREDENTIALS = True

CSRF_COOKIE_NAME = 'csrftoken'
CSRF_COOKIE_DOMAIN = '.example.com'

SESSION_COOKIE_NAME = 'sessionid'
SESSION_COOKIE_DOMAIN = '.example.com'

LANGUAGE_COOKIE_NAME = 'language'
LANGUAGE_COOKIE_DOMAIN = '.example.com'


SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = False
SESSION_COOKIE_HTTPONLY = False

MIDDLEWARE = (
    # main middleware you should use to configure correct data exchange
    # ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware', 
    "corsheaders.middleware.CorsMiddleware",
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    # ...
)

# if we use Django REST framework API in our project than we should use DRF based SessionAuthentication
REST_FRAMEWORK = {
    # ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
    ),
    # ...
}

INSTALLED_APPS = (
    # ...
    'corsheaders',
    # ...
)

In the file above we had to write down next settings, lets explain what we have.

DOMAIN_NAME - The name of the main domain.

ALLOWED_HOSTS - A list of strings representing the host/domain names that this Django site can serve. A value beginning with a period can be used as a subdomain wildcard: '.example.com' will match example.com, www.example.com, and any other subdomain of example.com.

CORS_ALLOWED_ORIGINS - A list of origins that are authorized to make cross-site HTTP requests. The origins in this setting will be allowed, and the requesting origin will be echoed back to the client in the Access-Control-Allow-Origin header. Defaults to [].

CORS_EXPOSE_HEADERS - The list of extra HTTP headers to expose to the browser, in addition to the default safelist headers. If non-empty, these are declared in the Access-Control-Expose-Headers header. Defaults to [].

CORS_ALLOW_CREDENTIALS - If True, cookies will be allowed to be included in cross-site HTTP requests. This sets the Access-Control-Allow-Credentials header in preflight and normal responses. Defaults to False.

CSRF_COOKIE_NAME - The name of the cookie to use for the CSRF authentication token. This can be whatever you want (as long as it’s different from the other cookie names in your application).

SESSION_COOKIE_NAME - The name of the cookie to use for sessions. Rules are the same.

CSRF_COOKIE_DOMAIN - The domain to be used when setting the CSRF cookie. This can be useful for easily allowing cross-subdomain requests to be excluded from the normal cross-site request forgery protection. It should be set to a string such as ".example.com" to allow a POST request from a form on one subdomain to be accepted by a view served from another subdomain.

SESSION_COOKIE_DOMAIN - The domain to use for session cookies. Set this to a string such as "example.com" for cross-domain cookies, or use None for a standard domain cookie.

LANGUAGE_COOKIE_NAME - The name of the cookie to use for the language cookie.

LANGUAGE_COOKIE_DOMAIN - The domain to use for the language cookie. Set this to a string such as ".example.com" for cross-domain cookies, or use None for a standard domain cookie.

SESSION_COOKIE_SECURE - Whether to use a secure cookie for the session cookie. If this is set to True, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection.

CSRF_COOKIE_SECURE - Whether to use a secure cookie for the CSRF cookie. If this is set to True, the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent with an HTTPS connection.

CSRF_COOKIE_HTTPONLY - Whether to use HttpOnly flag on the CSRF cookie. If this is set to True, client-side JavaScript will not be able to access the CSRF cookie.

SESSION_COOKIE_HTTPONLY - Whether to use the HttpOnly flag on the session cookie. If this is set to True, client-side JavaScript will not be able to access the session cookie.

The most common errors that occur when you run your Django project using subdomains and how to fix it

When we configure all these settings in the Django project, we have to check additional settings in our server configuration.

  • If we use Django templates, we should add line{csrf_token} in Django templates then Django handles the functionalities of csrf-token.

  • If we use DRF API+React and want to use HTTP methods that can change data in our server database (unsafe methods, like adding a new product via method POST), we should put csrf-token to all headers settings we send. Otherwise, we will get 403 HTTP Forbidden errors for any unsafe methods like POST, PUT, PATCH, or DELETE.

We use some code like this to avoid this mistake:

fetch(url, {
    credentials: 'include',
    method: 'POST',
    mode: 'same-origin',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'X-CSRFToken': csrftoken
    },
    body: {}
   })
  }
  • We need to check the server configuration to allow all used HTTP methods and allow all necessary headers:
...
add_header "Access-Control-Allow-Origin" '*';
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD, PATCH, DELETE, PUT";
add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept, X-CSRF-Token";
...
line

Looking for an enthusiastic team?