web

Python Django

Python Django

In this quickstart, you will build an application with Python and Django and integrate it with FusionAuth. The application is for ChangeBank, a global leader in converting dollars into coins. It will have areas reserved for logged in users and public-facing sections.

Find the Docker Compose file and source code for the complete application at https://github.com/FusionAuth/fusionauth-quickstart-python-django-web.

Prerequisites

For this Quickstart, you’ll need:

General Architecture

While this sample application doesn’t have login functionality without FusionAuth, a more typical integration will replace an existing login system with FusionAuth.

In that case, the system might look like this before FusionAuth is introduced.

UserApplicationView HomepageClick Login LinkShow Login FormFill Out and Submit Login FormAuthenticates UserDisplay User's Account or OtherInfoUserApplication

Request flow during login before FusionAuth

The login flow will look like this after FusionAuth is introduced.

UserApplicationFusionAuthView HomepageClick Login Link (to FusionAuth)View Login FormShow Login FormFill Out and Submit Login FormAuthenticates UserGo to Redirect URIRequest the Redirect URIIs User Authenticated?User is AuthenticatedDisplay User's Account or OtherInfoUserApplicationFusionAuth

Request flow during login after FusionAuth

In general, you would introduce FusionAuth to normalize and consolidate user data, making it consistent and up-to-date and offloading your login security and functionality to FusionAuth.

Getting Started

Start with getting FusionAuth up and running and creating a new Django application.

Clone The Code

First, grab the code from the repository and change into that directory.

git clone https://github.com/FusionAuth/fusionauth-quickstart-python-django-web.git
cd fusionauth-quickstart-python-django-web

All shell commands in this guide can be entered in a terminal in this folder. On Windows, you need to replace forward slashes with backslashes in paths.

The files you’ll create in this guide already exist in the complete-application folder, if you prefer to copy them.

Run FusionAuth Via Docker

You’ll find a Docker Compose file docker-compose.yml and an environment variables configuration file .env in the root directory of the repo.

Assuming you have Docker installed, you can start FusionAuth on your machine with the following.

docker-compose up -d

This will start three containers, one each for FusionAuth, Postgres, and Elasticsearch.

Here you are using a bootstrapping feature of FusionAuth, called Kickstart. When FusionAuth starts for the first time, it will look at the kickstart/kickstart.json file and configure FusionAuth to your specified state.

If you ever want to reset the FusionAuth system, delete the volumes created by docker-compose by executing docker-compose down -v, then rerun docker-compose up -d.

FusionAuth will be configured with these settings:

You can log in to the FusionAuth admin UI and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

The .env and kickstart.json files contain passwords. In a real application, always add these files to your .gitignore file and never commit secrets to version control.

Create A Basic Django Application

Next, you’ll set up a basic Django project with a single app. While this guide builds a new Django project, you can use the same method to integrate your existing project with FusionAuth.

Set Up Your Environment

You should work in a virtual environment for this.

python3 -m venv venv && source venv/bin/activate

Using venv isolates Python dependencies for this project locally so that any dependencies used by other projects on your machine are not affected.

Create a requirements.txt file to list the project dependencies with the following content.

fusionauth-client==1.47.0
Authlib==1.2.0
python-dotenv==1.0.0
requests==2.31.0
Django==3.2.15
mozilla-django-oidc==2.0.0

Then install the dependencies into your virtual environment.

pip install -r requirements.txt

Create The Application

Create the default Django starter project.

django-admin startproject mysite

Now create a .env file in your mysite folder and add the following to it (note that this is a different environment file to the one in the root folder used by Docker for FusionAuth).

CLIENT_ID=e9fdb985-9173-4e01-9d73-ac2d60d1dc8e
CLIENT_SECRET=super-secret-secret-that-should-be-regenerated-for-production
ISSUER=http://localhost:9011
APP_SECRET_KEY=0386ffa9-3bff-4c75-932a-48d6a763ce77

Authentication

Create an app within your mysite project.

cd mysite
python manage.py startapp app
cd ..

Now you will work from the top down to set up authentication URLs in the project settings.py, then in the application urls.py, then the views.py, and finally, the HTML templates for the views.

Project Settings

Overwrite your mysite/mysite/settings.py contents with the following code.

import os
from pathlib import Path
from dotenv import find_dotenv, load_dotenv

ENV_FILE = find_dotenv()
if ENV_FILE:
    load_dotenv(ENV_FILE)

BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ['APP_SECRET_KEY']
DEBUG = True
ALLOWED_HOSTS = []

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app',
    'mozilla_django_oidc',
]

AUTHENTICATION_BACKENDS = ('mozilla_django_oidc.auth.OIDCAuthenticationBackend',)

OIDC_RP_CLIENT_ID = os.environ['CLIENT_ID']
OIDC_RP_CLIENT_SECRET = os.environ['CLIENT_SECRET']
OIDC_OP_AUTHORIZATION_ENDPOINT = os.environ['ISSUER'] + "/oauth2/authorize"
OIDC_OP_TOKEN_ENDPOINT = os.environ['ISSUER'] + "/oauth2/token"
OIDC_OP_USER_ENDPOINT = os.environ['ISSUER'] + "/oauth2/userinfo"
OIDC_RP_SCOPES = "openid profile email"
OIDC_RP_SIGN_ALGO = "RS256"
OIDC_OP_JWKS_ENDPOINT = os.environ['ISSUER'] + "/.well-known/jwks.json"
LOGIN_REDIRECT_URL = "http://localhost:8000/app/account/"
LOGOUT_REDIRECT_URL = "http://localhost:8000/app/"

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mysite.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': (os.path.join(BASE_DIR, 'app/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',
            ],
        },
    },
]

WSGI_APPLICATION = 'mysite.wsgi.application'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},
    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',},
    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',},
    {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',},
]

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

STATIC_URL = '/static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Here, your app and mozilla_django_oidc are added to the list of INSTALLED_APPS. The authentication library from Mozilla is set up by mozilla_django_oidc and the OIDC_OP_ constants imported from the .env file. This automatically handles the interaction with FusionAuth on the endpoints configured in this file and the matching ones configured in the kickstart.json file that you used to initialize FusionAuth.

Project URLs

Overwrite the mysite/mysite/urls.py file with the following code.

from django.contrib import admin
from django.urls import include, path
from django.views.generic import RedirectView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app/', include('app.urls')),
    path('oidc/', include('mozilla_django_oidc.urls')),
    path('', RedirectView.as_view(url='/app/', permanent=True)),
]

App URLs

From this point, you’ll create files in the mysite/app application folder, not in the main mysite/mysite project.

Create a mysite/app/urls.py file and set its content to match the project URLs.

from django.urls import path
from . import views

urlpatterns = [
    path('', views.app, name='app'),
    path('account/', views.account, name='account'),
    path('logout/', views.logout, name='logout'),
    path('change/', views.change, name='change'),
]

App Views

Now overwrite the mysite/app/views.py file. The code below will create three pages: the home page, the account page, and the make change page.

from json import loads
from math import ceil
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.conf import settings

def app(request):
    if not request.user.is_authenticated:
        return render(request, template_name='home.html')
    return redirect('account')

def account(request):
    if not request.user.is_authenticated:
        return redirect('oidc_authentication_init')
    return render(request, 'account.html', {'email': request.user.email})

def logout(request):
    django_logout(request)
    return redirect('app')

def change(request):
    if not request.user.is_authenticated:
        return redirect('oidc_authentication_init')
    change = { "error": None }
    if request.method == 'POST':
        dollar_amt_param = request.POST.get("amount")
        try:
            if dollar_amt_param:
                dollar_amt = float(dollar_amt_param)
                nickels = int(dollar_amt / 0.05)
                pennies = ceil((dollar_amt - (0.05 * nickels)) / 0.01)
                change["total"] = "{:,.2f}".format(dollar_amt)
                change["nickels"] = "{:,d}".format(nickels)
                change["pennies"] = "{:,d}".format(pennies)
        except ValueError:
            change["error"] = "Please enter a dollar amount"
    return render(
        request,
        'change.html',
        {
            'email': request.user.email,
            'change': change
        }
    )

Here’s what these views do:

Customization

The final step is to create the HTML pages and style them with CSS. Copy the static and templates folders into your app. Use the appropriate command for your operating system.

# linux, mac
cp -r complete-application/mysite/app/static mysite/app
cp -r complete-application/mysite/app/templates mysite/app

# windows
xcopy /e complete-application\mysite\app\static mysite\app
xcopy /e complete-application\mysite\app\templates mysite\app

There are only two lines in these templates related to authentication. In the home.html template, there is a login button.

<a class="button-lg" href="{% url 'oidc_authentication_init' %}">Login</a>

This URL is made available by the Mozilla plugin in the settings file. It directs the user to FusionAuth to log in, which then redirects back to your app account page.

In the account and change pages, there is a logout form.

<form id="logoutForm" class="button-lg"  action="{% url 'oidc_logout' %}" method="post">
  {% csrf_token %}
  <a class="button-lg" href="#" onclick="document.getElementById('logoutForm').submit();">Log out</a>
</form>

Log out has to be a form that submits a POST request to FusionAuth. Here you use an anchor element that is easy to style, but if your users might disable JavaScript, you could use a traditional form input element.

Run The Application

Update the default SQLite database and start your server.

python mysite/manage.py migrate
python mysite/manage.py runserver

You can now browse to the Django app.

Log in using richard@example.com and password. The change page will allow you to enter a number and see the result of the POST. Log out and verify that you can’t browse to the account page.

Next Steps

This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you’re going to want to do.

FusionAuth Customization

FusionAuth gives you the ability to customize just about everything to do with the user’s experience and the integration of your application. This includes:

Security

Tenant And Application Management