Introduction:
Hello everyone, Asif Khan here. Recently, I encountered a persistent issue with my website’s contact form. It seemed that malicious individuals were exploiting it to send links, possibly leading to harmful content. Determined to secure my platform and prevent such abuse, I delved into crafting a robust solution. Today, I’m excited to share with you the creation of a highly secure contact form for my website. Through diligent implementation of various security measures in Django, I’ve ensured that no one can slip harmful links through the cracks anymore. Let’s dive into the details of how I tackled this challenge and fortified my website against unwanted intrusions.
Step-by-Step Guide:
- Installation:First, ensure you have Django installed. You can install Django using pip:
pip install django
- Create Django Project:Start by creating a new Django project. Open your terminal or command prompt and run:
django-admin startproject myproject
Replacemyproject
with your desired project name.- Create Django App:Next, create a new Django app within your project. Navigate into your project directory and run:
python manage.py startapp contact_form
Create Models , Forms, and Views:
Now, let’s create the necessary files for handling the contact form.
- Models (
models.py
):
# contact_form/models.py
from django.db import models
class ContactMessage(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
subject = models.CharField(max_length=200)
message = models.TextField()
def __str__(self):
return self.subject
Sure! Let’s break down the code in simpler terms:
- Django Imports: The line
from django.db import models
brings in a special toolkit called Django. Think of Django as a box of tools that helps us build websites more easily. Themodels
inside Django are like blueprints for how we want to store information in our website’s database. - ContactMessage Model: Imagine we’re building a digital mailbox to receive messages from visitors.
ContactMessage
is like the design plan for our mailbox. It tells Django how to organize the messages we receive. - Fields:
name
: This is where we store the name of the person who sends us a message. It’s like the label on a letter saying who it’s from.email
: This is where we keep the email address of the person sending the message. It’s like the return address on an envelope.subject
: This is where we store the subject or title of the message. It’s like the subject line of an email.message
: This is where we store the actual content of the message. It’s like the main body of an email.
- __str__ Method: This is a special method that helps Django understand how to represent each message in a way that’s easy for us to understand. In this case, it returns the subject of the message. So, when we look at our list of messages, we’ll see each one’s subject first.
So, in simple terms, this code creates a structure for our website to store messages from visitors. Each message will have a name, email, subject, and message content. This makes it easy for us to read, organize, and reply to messages on our website.
Forms (forms.py
):
# contact_form/forms.py
from django import forms
from django.core.exceptions import ValidationError
from .models import ContactMessage
class ContactForm(forms.ModelForm):
class Meta:
model = ContactMessage
fields = ['name', 'email', 'subject', 'message']
- Django Imports: The line
from django import forms
imports a set of tools from Django specifically designed to create and handle forms. Forms are like digital versions of paper forms that visitors can fill out on our website. - ContactForm Class: Just like the
ContactMessage
class we discussed earlier,ContactForm
is another blueprint. But this time, it’s a blueprint for how our contact form will look and work on our website. - Meta Class: Inside
ContactForm
, there’s a special class calledMeta
. This class provides additional information about the form.model = ContactMessage
: This line tells Django that our form is based on theContactMessage
model we defined earlier. So, our form will have fields forname
,email
,subject
, andmessage
, just like theContactMessage
model.fields = ['name', 'email', 'subject', 'message']
: This line specifies which fields from theContactMessage
model should be included in the form. So, our form will have fields for the name, email, subject, and message.
So, when visitors come to our website and want to send us a message, they’ll see a form with fields for their name, email, subject, and message. When they fill out the form and submit it, Django will handle saving the information into our database using the structure defined in the ContactMessage
model. This makes it easy for us to collect and manage messages from visitors.
Views (views.py
):
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_protect
from .forms import ContactForm
from django.shortcuts import render
from django.utils.html import escape
import re
@csrf_protect
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
message = form.cleaned_data['message']
# Regular expression to match URLs
url_pattern = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+')
if url_pattern.search(message):
return JsonResponse({'message': 'Links are not allowed in the message.'}, status=400)
# Save sanitized data
contact_instance = form.save(commit=False)
# You may sanitize the message here if needed
contact_instance.save()
message = 'Thank you for contacting us! We will get back to you as soon as possible.'
return JsonResponse({'message': escape(message)})
else:
return JsonResponse({'message': 'Form submission failed. Please check the entered data.'}, status=400)
form = ContactForm()
return render(request, 'contact.html', {'form': form})
- Django Imports: This section imports various components from Django that we’ll need in our view function. These include
JsonResponse
for returning JSON responses,csrf_protect
for protecting against CSRF attacks,ContactForm
from our forms file,render
for rendering HTML templates, andescape
for escaping HTML entities. - CSRF Protection: The
@csrf_protect
decorator helps protect against Cross-Site Request Forgery (CSRF) attacks. This ensures that the form submissions are coming from our own site and not from a malicious source. - Contact View Function: This is the main view function that handles the contact page. When a user visits the contact page, this function gets called.
- Form Handling:
- POST Request Handling: If the user submits the contact form (via POST request), the function checks if the submitted form data is valid.
- Validation: If the form is valid, it checks if the message contains any URLs. If URLs are found, it returns a JSON response indicating that links are not allowed. Otherwise, it saves the form data to the database after sanitizing it.
- Response: It then returns a JSON response with a success message.
- Validation Failure: If the form is not valid, it returns a JSON response indicating that the form submission failed.
- GET Request Handling: If the request method is not POST (i.e., it’s a GET request), the function creates a new instance of the
ContactForm
and renders thecontact.html
template, passing the form object to the template for display.
In simpler terms, this code handles the submission of the contact form, validates the data, saves it to the database, and returns appropriate responses based on the outcome. It also renders the contact form page with an empty form for users to fill out.
Create Templates:
Create HTML templates for the base.html page inside the templates
directory of your app.
- Contact Form Template (
base.html
):
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap CSS link -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Your Website Title</title>
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<!-- Bootstrap JS link -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>
Create Templates:
Create HTML templates for the contact form html page inside the templates
directory of your app.
- Contact Form Template (
contact.html
):
{% extends 'base.html' %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<div id="messageDiv" class="mb-3"></div>
<h2 class="text-center mb-4">Contact Us</h2>
<form id="contactForm" method="post" action="{% url 'contact' %}" class="needs-validation" novalidate>
{% csrf_token %}
<div class="form-group">
<label for="id_name" class="sr-only">Name:</label>
<input type="text" name="name" id="id_name" class="form-control" placeholder="Your Name" required>
<div class="invalid-feedback">Please enter your name.</div>
</div>
<div class="form-group">
<label for="id_email" class="sr-only">Email:</label>
<input type="email" name="email" id="id_email" class="form-control" placeholder="Your Email" required>
<div class="invalid-feedback">Please enter a valid email address.</div>
</div>
<div class="form-group">
<label for="id_subject" class="sr-only">Subject:</label>
<input type="text" name="subject" id="id_subject" class="form-control" placeholder="Subject" required>
<div class="invalid-feedback">Please enter a subject.</div>
</div>
<div class="form-group">
<label for="id_message" class="sr-only">Message:</label>
<textarea name="message" id="id_message" class="form-control" placeholder="Your Message" required></textarea>
<div class="invalid-feedback">Please enter your message.</div>
</div>
<button type="button" id="submitBtn" class="btn btn-primary btn-block">
<span class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
Submit
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
document.getElementById('submitBtn').addEventListener('click', function () {
var form = document.getElementById('contactForm');
// Add 'was-validated' class to trigger custom validation styles
form.classList.add('was-validated');
if (form.checkValidity()) {
// Show loading spinner
document.getElementById('submitBtn').disabled = true;
document.getElementById('submitBtn').querySelector('span').classList.remove('d-none');
// AJAX request
var formData = new FormData(form);
var xhr = new XMLHttpRequest();
xhr.open('POST', '{% url 'contact' %}', true);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
var data = JSON.parse(xhr.responseText);
// Handle success
var successDiv = document.getElementById('messageDiv');
successDiv.innerHTML = '<div class="alert alert-success">' + data.message + '</div>';
// Clear success message after 5 seconds
clearMessage();
} else {
var errorData = JSON.parse(xhr.responseText);
// Handle error
document.getElementById('messageDiv').innerHTML = '<div class="alert alert-danger">' + errorData.message + '</div>';
// Clear error message after 5 seconds
clearMessage();
}
// Hide loading spinner
document.getElementById('submitBtn').disabled = false;
document.getElementById('submitBtn').querySelector('span').classList.add('d-none');
};
xhr.onerror = function () {
// Handle network error
document.getElementById('messageDiv').innerHTML = '<div class="alert alert-danger">An error occurred. Please try again later.</div>';
// Clear error message after 5 seconds
clearMessage();
// Hide loading spinner
document.getElementById('submitBtn').disabled = false;
document.getElementById('submitBtn').querySelector('span').classList.add('d-none');
};
xhr.send(formData);
}
});
function clearMessage() {
// Clear message after 5 seconds
setTimeout(function() {
var messageDiv = document.getElementById('messageDiv');
if (messageDiv) {
messageDiv.innerHTML = '';
}
}, 5000);
}
</script>
{% endblock %}
- Template Inheritance:
{% extends 'base.html' %}
– This line tells Django to use the template named'base.html'
as the base layout for this template. It means this template will inherit styles and structure from'base.html'
. - Content Block:
{% block content %} ... {% endblock %}
– This defines a block namedcontent
within the template. It’s a placeholder where content specific to this template can be inserted. In this case, it contains the HTML structure for the contact form. - HTML Structure: Inside the
content
block, there’s a container with a row and column layout, containing a card. This card represents the contact form. - Form Submission: The form has
id="contactForm"
and is set to submit to the URL named'contact'
(resolved by Django’surl
template tag). - AJAX Handling: When the submit button (
id="submitBtn"
) is clicked, an AJAX request is made to the'contact'
URL. This request sends the form data and expects a JSON response. Based on the response, it updates the message displayed on the page. - JavaScript Validation: JavaScript is used for form validation and handling form submission asynchronously. It checks if the form is valid before submitting it and displays appropriate messages to the user.
Configure URLs:
Finally, configure URLs to route requests to the appropriate views.
- Project URLs (
urls.py
):
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('contact_form.urls')),
]
- Admin URL:
path('admin/', admin.site.urls)
– This line defines a URL pattern that directs requests starting with/admin/
to the Django admin interface. When a user visits/admin/
, Django will display the admin interface where administrators can manage the site’s data. - App URL:
path('', include('app.urls'))
– This line defines a URL pattern that includes URLs from another application named'app'
. Theinclude()
function allows us to include URL patterns from other URL configurations. In this case, it includes URLs defined in theurls.py
file of the'app'
application.''
: This empty string signifies the root URL. When a user visits the root URL of the website (e.g.,https://example.com/
), Django will include the URLs defined in the'app.urls'
module. This is typically where the main views and functionality of the website are defined.'app.urls'
: This is the module containing the URL patterns for the'app'
application. By including these URLs, we can organize the URL configuration of our project into smaller, modular components, making it easier to manage and maintain.
Overall, this Django URLs configuration maps specific URL patterns to different parts of our Django project: the admin interface and the views and functionality defined in the 'app'
application. It’s the backbone of how Django routes incoming requests to the appropriate views or handlers.
App URLs (urls.py
inside contact_form
directory):
from django.urls import path
from . import views
urlpatterns = [
path('', views.contact, name='contact'),
]
Congratulations! You’ve successfully built a secure contact form in Django. By following these steps, you’ve implemented various security measures to prevent URL injection and ensure the integrity of user-submitted data.