One can build a web application with the Python programming language, and there
are several options for choosing a web framework. Two good frameworks are Django
and Flask.
In the previous post "Securing Your Flask Application Using Kratos and Keto",
we discussed how to build a Flask application with authentication. Let's do the
same thing for Django.
Django is a web framework for perfectionists with deadlines and can help build
web applications quickly. It has its authentication and permissions framework,
admin interface, and a lot of integrations with popular relational database
systems such as PostgreSQL, Percona Server, SQLite, MySQL, and MariaDB.
I love building web apps using Django because it was my first web framework, and
I love its ecosystem. In this tutorial, we'll build a simple one-page
application with authentication powered by the Ory Network.
Let's start hacking, huh?
Bootstrap Django project
Django has many ways to bootstrap a project:
- Django Cookiecutter is
a framework for jumpstarting production-ready Django apps. It's a useful
framework generating everything you need for the Django project, including
docker support, integration with Cloud storage such as S3, and more.
django-admin command. We'll use it because we want to build something
simple, and it would be easier for local development.
To run this tutorial it is also recommended to
install Poetry to manage python
dependencies.
mkdir django-ory-cloud
cd django-ory-cloud
poetry init
This command will guide you through creating your pyproject.toml config.
Package name [django-ory-cloud]:
Version [0.1.0]:
Description []: An example django application with the Ory Network as an Authentication backend
Author [John Doe, n to skip]: John Doe
License []:
Compatible Python versions [^3.10]:
Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Generated file
[tool.poetry]
name = "django-ory-cloud"
version = "0.1.0"
description = "An example django application with the Ory Network as an Authentication backend"
authors = ["John Doe"]
[tool.poetry.dependencies]
python = "^3.10"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Do you confirm generation? (yes/no) [yes] yes
Installing Django
poetry add Django
Creating virtualenv django-ory-cloud-0UeuvAWs-py3.10 in /Users/gen1us2k/Library/Caches/pypoetry/virtualenvs
Using version ^4.0.4 for Django
Updating dependencies
Resolving dependencies... (1.0s)
Writing lock file
Package operations: 3 installs, 0 updates, 0 removals
• Installing asgiref (3.5.1)
• Installing sqlparse (0.4.2)
• Installing django (4.0.4)
Creating project
poetry run django-admin startproject mysite
It generates the following project structure and you can learn more about the
structure
here
.
├── mysite
│ ├── manage.py
│ └── mysite
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── poetry.lock
└── pyproject.toml
2 directories, 8 files
Now it's time to install additional dependencies such as
django-environ to enable
configuration via environment variables and
django-ory-auth to enable
integration with the Ory Network
poetry add django-environ django-ory-auth
Using version ^0.8.1 for django-environ
Using version ^0.1.3 for django-ory-auth
Updating dependencies
Resolving dependencies... (2.4s)
Writing lock file
Package operations: 7 installs, 0 updates, 0 removals
• Installing certifi (2021.10.8)
• Installing charset-normalizer (2.0.12)
• Installing idna (3.3)
• Installing urllib3 (1.26.9)
• Installing requests (2.27.1)
• Installing django-environ (0.8.1)
• Installing django-ory-auth (0.1.3)
poetry shell
cd mysite
Enabling django-ory-auth package
On the previous step we installed django-ory-auth package. We need to add it
to the INSTALLED_APPS by editing mysite/settings.py file.
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ 'django_ory_auth',
]
Let's discuss the next steps of our integration. Django-ory-auth package comes
with a context processor and an authentication backend. The context processor
provides additional context such as login_url, logout_url, and many more to use
in templates. The authentication backend replaces the default Django
authentication backends because we store identity data in the remote identity
provider.
It works fine with AUTH_USER_MODEL and Django's permission framework. You can
still use is_staff and is_superuser fields of the default user model.
Add this to the mysite/settings.py file:
AUTHENTICATION_BACKENDS = [
"django_ory_auth.backend.OryBackend",
]
Django's default settings do not have an out-of-the-box configuration for
templates, so we need to make additional changes. This example uses
mysite/templates folder to store templates, and we need to make further
changes.
Edit mysite/settings.py:
import os
import environ
env = environ.Env()
...
TEMPLATES
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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',
'django_ory_auth.context.processor',
],
},
...
Set up the Ory Network
Okay, for the next steps we need to have an Ory Network project and install the
Ory CLI.
- Create Ory Network project
- Install Ory CLI
You can create a new Ory Network project with the Ory CLI:
ory create project --name example
To connect your Ory Network project, you need to copy the Ory SDK URL. You can
find it by opening Connect from the sidebar of the
Ory Console. The URL follows this pattern
https://projectid.projects.oryapis.com. We can use the playground project for
demo purposes.
Add it to the mysite/settings.py
...
ORY_SDK_URL=env("ORY_SDK_URL", default='https://playground.projects.oryapis.com')
ORY_UI_URL=env("ORY_SDK_URL", default='https://playground.projects.oryapis.com/ui')
We have now added authentication to the Django backend!
Build a simple webpage
Adding a new webpage in the Django framework is straightforward. One needs to
create
- A template
- A view
- Add a view to the URL patterns
Let's start with the template. Create templates/base.html with the following
content:
{% load static i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{% block title %}{% endblock title %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="{% static 'images/favicons/favicon.ico' %}">
{% block css %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link href="{% static 'css/project.css' %}" rel="stylesheet">
{% endblock %}
{# Placed at the top of the document so pages load faster with defer #}
{% block javascript %}
<script defer src="{% static 'js/vendors.min.js' %}"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.min.js" integrity="sha512-OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script defer src="{% static 'js/project.js' %}"></script>
{% endblock javascript %}
</head>
<body>
<div class="mb-1">
<nav class="navbar navbar-expand-md navbar-light bg-light">
<div class="container-fluid">
<button class="navbar-toggler navbar-toggler-right" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="{% url 'home' %}"></a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="{% url 'home' %}">Home <span class="visually-hidden">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="">About</a>
</li>
{% if request.user.is_authenticated %}
<li class="nav-item">
{# URL provided by django-allauth/account/urls.py #}
<a class="nav-link" href="">{% translate "My Profile" %}</a>
</li>
<li class="nav-item">
{# URL provided by django-allauth/account/urls.py #}
<a class="nav-link" href="{{ logout_url }}">{% translate "Sign Out" %}</a>
</li>
{% else %}
<li class="nav-item">
<a id="sign-up-link" class="nav-link" href="{{ signup_url }}">{% translate "Sign Up" %}</a>
</li>
<li class="nav-item">
<a id="log-in-link" class="nav-link" href="{{ login_url }}">{% translate "Sign In" %}</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
</div>
<div class="container">
{% if messages %}
{% for message in messages %}
<div class="alert alert-dismissible {% if message.tags %}alert-{{ message.tags }}{% endif %}">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% block content %}
<p>Use this document as a way to quick start any new project.</p>
{% endblock content %}
</div>
{% block modal %}{% endblock modal %}
{% block inline_javascript %}
{% comment %}
Script tags with only code, no src (defer by default). To run
with a "defer" so that you run inline code:
<script>
window.addEventListener('DOMContentLoaded', () => {});
</script>
{% endcomment %}
{% endblock inline_javascript %}
</body>
</html>
The example uses TemplateView in this example because of its simplicity. Edit
mysite/urls.py and add the following lines:
// ...
from django.views.generic import TemplateView
urlpatterns = [
path('admin/', admin.site.urls),
path('', TemplateView.as_view(template_name='base.html'), name='home'),
]
Run it and test it
At this point, we have everything set up for the Django project.
Start Django:
export ORY_SDK_URL=http://127.0.0.1:4000/.ory
export ORY_UI_URL=http://127.0.0.1:4000/.ory/ui
poetry run ./manage.py runserver
Start Ory Proxy:
export ORY_SDK_URL=https://playground.projects.oryapis.com
ory proxy http://127.0.0.1:8000
Open http://127.0.0.1:4000 and click Login to get started
Use it with Ory Kratos self-hosted
You can use django-ory-auth with Ory Kratos self-hosted. You can run
everything using the following docker-compose.yml
version: "3.7"
services:
postgres-kratos:
image: postgres:9.6
environment:
- POSTGRES_USER=kratos
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=kratos
networks:
- intranet
kratos-migrate:
image: oryd/kratos:v0.9.0-alpha.3
links:
- postgres-kratos:postgres-kratos
environment:
- DSN=postgres://kratos:secret@postgres-kratos:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
networks:
- intranet
volumes:
- type: bind
source: ./kratos
target: /etc/config/kratos
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
kratos:
image: oryd/kratos:v0.9.0-alpha.3
links:
- postgres-kratos:postgres-kratos
environment:
- DSN=postgres://kratos:secret@postgres-kratos:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
ports:
- "4433:4433"
- "4434:4434"
volumes:
- type: bind
source: ./kratos
target: /etc/config/kratos
networks:
- intranet
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
kratos-selfservice-ui-node:
image: oryd/kratos-selfservice-ui-node:latest
environment:
- KRATOS_PUBLIC_URL=http://kratos:4433/
- KRATOS_BROWSER_URL=http://127.0.0.1:4433/
networks:
- intranet
ports:
- "4455:3000"
restart: on-failure
mailslurper:
image: oryd/mailslurper:latest-smtps
ports:
- "4436:4436"
- "4437:4437"
networks:
- intranet
networks:
intranet:
Run Django
export ORY_SDK_URL=http://127.0.0.1:4433
export ORY_UI_URL=http://127.0.0.1:4455
./manage.py runserver
Open http://127.0.0.1:8000 and click Login or Signup to get started.
Next steps
- Add Two Factor Authentication (2FA) to your App
- Add social signin features
- Configure more secure password policies
- Implement email and phone verification and account Activation
- Deploy Django App to production