Handling host validation dynamically in Django
Tags: webdev, django, python, programming
Published: 2024-02-12 09:47:11

Django is a fantastic tool for making top-notch web applications in Python. It has lots of built-in security features to stop attacks like the HTTP host header attack. To deal with this, developers usually have to list all the host/domain names they want to allow and assign them to ALLOWED_HOST in the settings.py configuration file. 
It's a pretty secure way to do things, but it can be a pain because you have to manually update this list every time you want to add a new host or change an existing one.

**Prerequisite**:

- A good understanding of Python programming language
- A decent experience using the Django framework

In this article, we'll explore how Django Dynamic Host helps simplify the process. We'll dive into its usage, features, and benefits.

## Usage
It's pretty straightforward to make use of a Django dynamic host in your Django project. Simply begin by installing the library.

> pip install django-dynamic-host

Next, you'll need to add Django dynamic host to your list of installed app
```
INSTALLED_APPS= [ 
   … 
  "dynamic_host",
]
```
After the above step, we add the middleware with which the library ships with to the top of the MIDDLEWARE settings. This middleware must sit at the top of all middleware listed.

```
MIDDLEWARE = [
"dynamic_host.middleware.AllowedHostMiddleWare",
…
]
```

Then go ahead and set the ALLOWED_HOST list to accept all host
```
ALLOWED_HOST = ["*"]
```
Kabrish! That is all you have to do to set up the library. If you run the application right now no host would be allowed on DEBUG=False but on DEBUG=True Django dynamic host by default allows localhost and 127.0.0.1.
What is left for you is to point to Django dynamic host on how to resolve whether a host is allowed or not: There are multiple ways to do this. Let's explore them one after the other.

**Writing a function handler**
This is the best approach to resolve domains/hosts dynamically from the database or anywhere else. This can be a function that sits anywhere in your application module but should sit in a module same level as settings.py as best practice. Django dynamic hosts expect the function to accept two arguments (host, request) and return a boolean value. Then assign the DYNAMIC_HOST_RESOLVER_FUNC in settings.py to the dotted path of the function.

> - project/foo.py

```
def dynamic_host_handler(host, request,**kwargs):
    """
        add some logic to check domain in database 
        or some inmemory database system or caching system...     
        this is totally up to you
    """
    if cache.exists(host):
        return True
    elif Model.objects.filter(domain=host).exists():
        save_to_cache(host)
        return True
    return False
```

The host parameter is a string domain name e.g."foo.com" and the request parameter is an instance of HTTPRequest. The above function can have any implementation but must return a boolean value of True or False: if the value returned is True it means that the host passed in is valid, else it means the host is not valid and should be blocked. Making the function value return only True will make every domain valid.
After this add the below to settings.py

```
DYNAMIC_HOST_RESOLVER_FUNC="project.foo.dynamic_host_handler"
```
And that is it you're done…. Dynamic hosts will then call your function anytime there is a need to validate hosts.

**Using DYNAMIC HOST DEFAULT HOSTS**
To add a list of valid hostname strings just like it is done in ALLOWED_HOSTS you can do so using the settings DYNAMIC_HOSTS_DEFAULT_HOSTS like so
```
DYNAMIC_HOSTS_DEFAULT_HOSTS=['foo.app', 'x.foo.com']
```
**Using DYNAMIC HOST ALLOW ALL**
Although it is not recommended to open up to all hosts. You may eventually want to do so in some test cases during development. Setting DYNAMIC_HOST_ALLOW_ALL to True opens your backend to all hosts.
```
DYNAMIC_HOST_ALLOW_ALL=True
```
**Using DYNAMIC HOST ALLOW SITES**
Setting this value to True makes Django dynamic host aware of you adding contrib.sites to your installed app. That way sites created via the sites model are automatically allowed.
```
DYNAMIC_HOST_ALLOW_SITES=True
```

## **How Django dynamic host works**
Firstly, Django dynamic host will verify if you've set `DYANMIC_HOST_ALLOW_ALL` to True. If you have, it won't bother checking the remaining settings; it simply proceeds to allow all hosts to access the application. However, if you haven't set `DYANMIC_HOST_ALLOW_ALL` to true, it proceeds to check if the hostname is included in `DYNAMIC_HOSTS_DEFAULT_HOSTS`, or if the host is registered in one of your Django sites, or if the function resolver returns true for the hostname. If none of these conditions are met, it disallows the hostname from accessing the application.

## **Conclusion**
Django Dynamic Host offers a robust solution for dynamically validating hostnames or simply handling host validation by writing your own rules in Django. So why struggle with manual updates to `ALLOWED_HOSTS` when you can leverage the power of Django Dynamic Host?
Here is a link to the GitHub repo of the Open source d[jango dynamic host library](https://github.com/goodnewsj62/django-dynamic-host) and the PyPI link also [dyango-dynamic-host](https://pypi.org/project/django-dynamic-host/) you can take a look at the source code and contributions are welcome