django logo
python logo

Django Authentication By Example

Published on June 26, 2024
Photo of Juan Cruz Martinez
Juan Cruz MartinezStaff Developer Advocate

This Django guide will help you learn how to secure a Django application using token-based authentication. You'll learn how to integrate Auth0 with Django to implement the following security features:

  • How to add user login, sign-up, and logout to Django applications.
  • How to get user profile information to personalize Django views.
  • How to protect Django views from unauthorized access.
  • How to make API calls from Django to request data from a protected API.

This guide uses Python version 3.10 and the Authlib library, which provides developers with a high-level API to handle the integration with OAuth and OpenID Connect servers from a low level specification implementation to integration with the major Python frameworks.

How Does Auth0 Work?

With the help of Auth0, you don't need to be an expert on identity protocols, such as OAuth 2.0 or OpenID Connect, to understand how to secure your web application stack.

You first integrate your Django application with Auth0. Your application will then redirect users to an Auth0 customizable login page when they need to log in. Once your users log in successfully, Auth0 redirects them back to your Django app, returning JSON Web Tokens (JWTs) with their authentication and user information.

Get the Starter Application

We have created a starter project using the startproject command to help you learn Django security concepts through hands-on practice. You can focus on building Django views and services to secure your application.

Start by cloning the web-app_django_python_hello-world repository on its starter branch:

COMMAND
git clone -b starter [email protected]:auth0-developer-hub/web-app_django_python_hello-world.git

Once you clone the repo, make web-app_django_python_hello-world your current directory:

COMMAND
cd web-app_django_python_hello-world

Next, to work with the project you will create a virtual environment:

COMMAND
python3 -m venv venv

Activate the virtual environment:

MACOS
WINDOWS
LINUX
source venv/bin/activate

Install the project dependencies as follows:

COMMAND
pip install -U pip && pip install -r requirements.txt
Warning: If you are having problems installing the version of Django listed on the requirements.txt this could be related to the version of Python you are currently using. See the FAQ for the Python versions supported by each version of Django

This starter Django project offers a functional application with views and services to hydrate the user interface. It is important to notice that the service implemented in the starter project simulates the external API by placing the responses directly on the code. Later on you'll integrate this Django application with a real API server using a backend technology of your choice.

In this guide you won't get into the details of configuring a database with Django. However, you'll set up the default sqlite database and apply migrations to get the application working without warnings. Run the command bellow to apply the migrations:

COMMAND
python manage.py migrate

You can get more information about databases with Django by reading the official documentation on database setup.

Before you can run your application there's a final setup you must do. You'll need to setup a secret key for your application. To do so, create a key like so:

COMMAND
python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'

And now you need to export this key to your environment. So copy the result from your last step and run the following command using it:

COMMAND
export DJANGO_SECRET_KEY=<generated-key>

Once that key is exported you can execute this command to run your Django application:

COMMAND
python manage.py runserver 4040

You'll see the following output on the command line:

Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
June 11, 2024 - 14:35:37
Django version 5.0.6, using settings 'webapp.settings'
Starting development server at http://127.0.0.1:4040/
Quit the server with CONTROL-C.

Now if you visit http://127.0.0.1:4040/ to access the application running and you should see something like this:

django home page app

Configure Django with Auth0

Follow these steps to get started with the Auth0 Identity Platform quickly:

Sign up and create an Auth0 Application

A free account also offers you:

During the sign-up process, you create something called an Auth0 Tenant, representing the product or service to which you are adding authentication.

Once you sign in, Auth0 takes you to the Dashboard. In the left sidebar menu, click on "Applications".

Then, click the "Create Application" button. A modal opens up with a form to provide a name for the application and choose its type. Use the following values:

Name
Auth0 Django Web Application
Application Type
Regular Web Applications
Regular Web Applications

Click the "Create" button to complete the process. Your Auth0 application page loads up.

In the next step, you'll learn how to help Django and Auth0 communicate.

What's the relationship between Auth0 Tenants and Auth0 Applications?

Let's say that you have a photo-sharing Django app called "Djangogram". You then would create an Auth0 tenant called djangogram. From a customer perspective, Djangogram is that customer's product or service.

Now, say that Djangogram is available on three platforms: web as a single-page application and Android and iOS as a native mobile application. If each platform needs authentication, you need to create three Auth0 applications to provide the product with everything it needs to authenticate users through that platform.

Djangogram users belong to the Auth0 Djangogram tenant, which shares them across its Auth0 applications.

Create a communication bridge between Django and Auth0

When using the Auth0 Identity Platform, you don't have to build login forms. Auth0 offers a Universal Login Page to reduce the overhead of adding and managing authentication.

How does Universal Login work?

Your Django application will redirect users to Auth0 whenever they trigger an authentication request. Auth0 will present them with a login page. Once they log in, Auth0 will redirect them back to your Django application. For that redirecting to happen securely, you must specify in your Auth0 Application Settings the URLs to which Auth0 can redirect users once it authenticates them.

As such, click on the "Settings" tab of your Auth0 Application page, locate the "Application URIs" section, and fill in the following values:

Allowed Callback URLs
http://127.0.0.1:4040/callback

The above value is the URL that Auth0 can use to redirect your users after they successfully log in.

Allowed Logout URLs
http://127.0.0.1:4040

The above value is the URL that Auth0 can use to redirect your users after they log out.

Allowed Web Origins
http://127.0.0.1:4040

Click the "Save Changes" button at the bottom of the page.

Do not close this page yet. You'll need some of its information in the next section.

Add the Auth0 configuration variables to Django

From the Auth0 Application Settings page, you need the Auth0 Domain and Client ID values to allow your Django application to use the communication bridge you created.

What exactly is an Auth0 Domain, an Auth0 Client ID, and an Auth0 Client Secret?

Domain

When you created a new Auth0 account, Auth0 asked you to pick a name for your tenant. This name, concatenated with .auth0.com, is your Auth0 Domain. It's the base URL that you will use to access the Auth0 APIs and the URL where you'll redirect users to log in.

You can also use custom domains to allow Auth0 to do the authentication heavy lifting for you without compromising your branding experience.

Client ID

Each application is assigned a Client ID upon creation, which is an alphanumeric string, and it's the unique identifier for your application (such as q8fij2iug0CmgPLfTfG1tZGdTQyGaTUA). You cannot modify the Client ID. You will use the Client ID to identify the Auth0 Application to which the Django application needs to connect.

Warning: Another critical piece of information present in the "Settings" is the Client Secret. This secret protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources.

Head back to your Auth0 application page and click on the "Settings" tab.

Locate the "Basic Information" section and follow these steps to get the Auth0 Domain, Auth0 Client ID, and Auth0 Client Secret values:

Auth0 application settings to enable user authentication

When you enter a value in the input fields present on this page, any code snippet that uses such value updates to reflect it. Using the input fields makes it easy to copy and paste code as you follow along.

As such, enter the "Domain", "Client ID" and "Client Secret" values in the following fields to set up your web application in the next section:

For security, these configuration values are stored in memory and only used locally. They are gone as soon as you refresh the page! As an extra precaution, we recommend that you should use values from an Auth0 test application instead of a production one.

These variables let your Django application identify as an authorized party to interact with the Auth0 authentication server.

You need to create an .env file to hold these variables file under the Django project directory as follows:

COMMAND
touch .env

Now, update the .env you just created by adding the following:

.env
AUTH0_DOMAIN=AUTH0-DOMAIN
AUTH0_CLIENT_ID=AUTH0-CLIENT-ID
AUTH0_CLIENT_SECRET=AUTH0-CLIENT-SECRET

You'll use these values in the next step.

Set up the OAuth and OpenID Connect with Authlib

To integrate your Django application with Auth0, you'll use the Authlib library, which will handle all the integration with the OAuth and OpenID Connect server for you.

Next, let's install and integrate the library into the application.

Install new dependencies

For this integration you'll need two new libraries added into the requirements.txt file as follows:

requirements.txt
django ~= 5.0.3
requests ~= 2.31.0
django-environ ~= 0.11.2
authlib ~= 1.3.0

You should stop your server if it is still running, then install the libraries like so:

COMMAND
pip install -U pip && pip install -r requirements.txt

This will make these dependencies available to your project.

Initialize the Auth0 configuration

Let's make the environment variables accessible through the Django applications settings by updating the webapp/settings.py file as follows:

webapp/settings.py
import environ # 👈 new code
import os
from pathlib import Path
# 👇 new code
env = environ.Env(
# set casting, default value
DEBUG=(bool, True)
)
# 👆 new code
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATE_DIR = os.path.join(BASE_DIR, "webapp", "templates")
# 👇 new code
# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
# 👆 new code
# ... existing code
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG') # 👈 updated code
# ... existing code
# 👇 new code at the end of the file
# Auth0 Settingd
AUTH0_DOMAIN = env('AUTH0_DOMAIN')
AUTH0_CLIENT_ID = env('AUTH0_CLIENT_ID')
AUTH0_CLIENT_SECRET = env('AUTH0_CLIENT_SECRET')
LOGIN_URL = '/login'
# 👆 new code

What about the LOGIN_URL?

The variable LOGIN_URL is used by Django's @login_required decorator to redirect users to a given login page.

This variable is used when an user tries to access an protected view without being authenticated.

The user is redirected to the value present in the LOGIN_URL variable, preventing users from seeing an 404 page.

With the Auth0 variables in place, you can now set up authlib and register Auth0 as an OAuth provider as follows:

webapp/views.py
from django.http import HttpResponse
from django.template import loader
# 👇 new code
from django.conf import settings
from authlib.integrations.django_client import OAuth
# 👆 new code
from .models import Profile
from .services.message_service import MessageService
# 👇 new code
oauth = OAuth()
oauth.register(
"auth0",
client_id=settings.AUTH0_CLIENT_ID,
client_secret=settings.AUTH0_CLIENT_SECRET,
client_kwargs={
"scope": "openid profile email",
},
server_metadata_url=(f"https://{settings.AUTH0_DOMAIN}/"+
".well-known/openid-configuration"),
)
# 👆 new code
# ... existing code

Once you provide authlib with the Auth0 server details such as the client_id, client_secret, scope, and server_metadata_url, the library is fully set up and you can start creating the views you need to handle the authentication requests with the Auth0 Authentication Server.

What is the server_metadata_url?

When setting up the OAuth client to use Auth0 in your Django application, you'll need to pass along the URL that holds the OpenID configuration for your tenant.

The OpenID configuration URL provides the Authlibe OAuth() client with all the references to communicate with Auth0 and validate the tokens that it will receive.

The Authlib library bundles together OAuth and OpenID Connect (OIDC) to make it easier for you while setting up OAuth and OIDC. If you pass "openid" as part of your "scope" you need to pass the configuration URL in the server_metadatada_url.

Read "Obtaining OpenID Provider Configuration Information" from the OpenID Connect spec to learn more about OpenID Connect configuration information and discovery. To understand more about how Authlib handles OpenID in Django read Django OpenID Connect Client.

What about using scopes?

You'll notice you are passing scope as part of the OAuth client arguments. Here you're providing the OpenID Connect Scopes: openid profile email.

  • openid: This scope informs the Auth0 Authorization Server that the Client is making an OpenID Connect (OIDC) request to verify the user's identity. OpenID Connect is an authentication protocol.

  • profile: This scope value requests access to the user's default profile information, such as name, nickname, and picture.

  • email: This scope value requests access to the email and email_verified information.

The details of the OpenID Connect Scopes go into the ID Token. However, you can define custom API scopes to implement access control. You'll identify those custom scopes in the calls that your client applications make to that API. Auth0 includes API scopes in the access token as the scope claim value.

The concepts about API scopes or permissions are better covered in an Auth0 API tutorial such as "Use TypeScript to Create a Secure API with Node.js and Express: Role-Based Access Control".

Lastly, you need to create a new Auth0 authentication backend for Django, as Django doesn't know about the user information provided by Auth0, and you want to store some additional information in session like the access tokens.

Start by creating a new file auth0_backend.py in the /webapp folder:

COMMAND
touch webapp/auth0_backend.py

Then populate it with the following content:

webapp/auth0_backend.py
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.models import User
class Auth0Backend(BaseBackend):
def authenticate(self, request, token=None):
request.session["user"] = token
user_info = token.get('userinfo')
username = user_info.get('sub')
if not username:
raise ValueError('username can\'t be blank!')
# The format of user_id is
# {identity provider id}|{unique id in the provider}
# The pipe character is invalid for the django username field
# The solution is to replace the pipe with a dash
username = username.replace('|', '_')
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# Optionally you can add additional parameters when creating
# the Django user like the name, role, administrative
# priviledges and more. You can also opt to save the user into
# the database on each login request, syncronizing the latest
# changes done to the user in the Auth0 database
user = User(username=username)
user.save()
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None

By extendind the BaseBackend() and creatind Auth0Backend you'll be able to capture the token information coming from Auth0 during the authentication process then store it in session.

Additionally, if the user doesn't exist in the Django database, you create one with the given username. Note that Authlib has called .parse_id_token() automatically which allowed you to get "userinfo" in the token through token.get('userinfo') in the code above.

In the authenticate method you can add additional logic to import more values from Auth0 into the Django user database such as the name, the django administrator access, etc. You can read more about customizing authentication in Django in the official documentation.

Finally, you need to tell Django to use the new authentication backend, by changing a parameter in the Django settings.

webapp/settings.py
# ...existing code
AUTHENTICATION_BACKENDS = ['webapp.auth0_backend.Auth0Backend'] # 👈 new code

Create the Auth0 Callback view

With the Auth0 configuration implemented, you'll need to create a view to handle the authentication process.

webapp/views.py
# ... existing code
from django.conf import settings
# 👇 new code
from django.shortcuts import redirect
from django.urls import reverse
from django.contrib import auth
# 👆 new code
from authlib.integrations.django_client import OAuth
# ... existing code
oauth = OAuth()
oauth.register(
"auth0",
client_id=settings.AUTH0_CLIENT_ID,
client_secret=settings.AUTH0_CLIENT_SECRET,
client_kwargs={
"scope": "openid profile email",
},
server_metadata_url=(f"https://{settings.AUTH0_DOMAIN}/"+
".well-known/openid-configuration"),
)
# 👇 new code
def callback(request):
token = oauth.auth0.authorize_access_token(request)
user = auth.authenticate(request, token=token)
if user:
auth.login(request, user)
return redirect(request.build_absolute_uri(reverse("profile")))
return HttpResponse(status=400)
# 👆 new code
# ... existing code

Views, by default, are not attached to an endpoint. To do that, you need to map a URL to the view in the urls.py file.

webapp/urls.py
# ... existing code
from . import views # 👈 new code
urlpatterns = [
path('admin', views.admin, name='admin'),
# ... existing code
path('callback', views.callback, name='callback'), # 👈 new code
# ... existing code
path('django-admin', admin.site.urls),
]
# ... existing code

After the user attempts to log in, either if it was successful or not, Auth0 will redirect to the /callback endpoint and the router will call the view that matches with it in the application.

In the callback view you authenticate the user coming from the request, call the auth.login function to log the user in within the django framework and redirect the user to the profile endpoint.

In case there's an error, you'll respond with a 400 error status code.

Create the Login Button

You have created the callback view to handle a login flow in your app. Now you need to create a Login Button that will trigger the authentication flow.

Register the login view

For the user to be able to authenticate you need to define the login view as follows:

webapp/views.py
# ... existing code
# 👇 new code
def login(request):
return oauth.auth0.authorize_redirect(
request,
request.build_absolute_uri(reverse("callback")),
)
# 👆 new code
# ... existing code

The authorize_redirect method is part of the Auth0 client you built with authlib. This method is responsible for telling Auth0 that there's an user to authenticate and second to set the route for the user to be returned to once Auth0 finishes running the checks, in this application that route is the /callback view. You can read more on the authorize_redirect here.

Next, you need to assign a URL to the view by implementing the following definition:

webapp/urls.py
# ... existing code
urlpatterns = [
path('callback', views.callback, name='callback'),
# ... existing code
path('login', views.login, name='login'), # 👈 new code
# ... existing code
path('django-admin', admin.site.urls),
]
# ... existing code

Add the login button to the template

Now create a new template that will handle the authentication buttons in the Nav Bar:

COMMAND
touch webapp/templates/navigation/desktop/nav_bar_buttons.html

Next, fill it up with the following content:

webapp/templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="/login" class="button__login" >Log In</a>
</div>

And create an additional file for the mobile view:

COMMAND
touch webapp/templates/navigation/mobile/mobile_nav_bar_buttons.html

Next, fill it up with the following content:

webapp/templates/navigation/mobile/nav_bar_buttons.html
<div class="mobile-nav-bar__buttons">
<a href="/login" class="button__login" >Log In</a>
</div>

In both templates, you are creating a button labeled "Log In". When the users click on this button, they will make a GET request to /login endpoint.

For the Desktop template, make the following changes:

webapp/templates/navigation/desktop/nav_bar.html
<div class="nav-bar__container">
<nav class="nav-bar">
{% include './nav_bar_brand.html' %}
{% include './nav_bar_tabs.html' %}
{% include './nav_bar_buttons.html' %} <!-- 👈 new code -->
</nav>
</div>

And similarly for mobile:

webapp/templates/navigation/mobile/mobile_nav_bar.html
<div class="mobile-nav-bar__container">
<nav id="mobile-nav-bar" class="mobile-nav-bar">
{% include './mobile_nav_bar_brand.html' %}
{% include './menu_button.html' %}
<div id="mobile-menu" class="mobile-nav-bar__menu mobile-nav-bar__menu--closed">
{% include './mobile_nav_bar_tabs.html' %}
{% include './mobile_nav_bar_buttons.html' %} <!-- 👈 new code -->
</div>
</nav>
</div>

If you run your server and look at the main page http://127.0.0.1:4040, now you should see the Log In button on the nav bar like so:

Main page with log in button on the nav bar

When the user now presses the "Log In" button from the UI, the login view will redirect the user to the Auth0 Universal Login Page, where the user will be able to authenticate. After completion, Auth0 will redirect the user to the callback endpoint completing the authentication process.

Universal Login - Login page

Create the Sign-Up Button

Users are able to log in, but they also need to be able to Sign Up.

Register the signup view

Similarly to when you register the login view, you need to implement the signup view. The process only differs in that you will pass an additional parameter to Auth0, hinting the service to start the process on the Sign Up page.

webapp/views.py
# ... existing code
# 👇 new code
def signup(request):
return oauth.auth0.authorize_redirect(
request,
request.build_absolute_uri(reverse("callback")),
screen_hint="signup"
)
# 👆 new code
# ... existing code

Next, you need to assign a URL to the view by implementing the following definition:

webapp/urls.py
# ... existing code
urlpatterns = [
path('login', views.login, name='login'),
# ... existing code
path('signup', views.signup, name='signup'), # 👈 new code
# ... existing code
path('django-admin', admin.site.urls),
]
# ... existing code

When the user presses the "Sign Up" button will be redirected to a sign-up page instead of a login page, as shown below:

Universal Login - Sign Up page

Add the sign up button to the template

Open the webapp/templates/navigation/desktop/nav_bar_buttons.html and add a new button:

webapp/templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="/signup" class="button__sign-up">Sign Up</a> <!-- 👈 new code -->
<a href="/login" class="button__login" >Log In</a>
</div>

The same button needs to be added to the mobile view like so:

webapp/templates/navigation/mobile/mobile_nav_bar_buttons.html
<div class="mobile-nav-bar__buttons">
<a href="/signup" class="button__sign-up">Sign Up</a> <!-- 👈 new code -->
<a href="/login" class="button__login" >Log In</a>
</div>

And you should be able to see the Sign Up button on your navigation bar alongside the Log In button.

Main page with sign up button on the nav bar

Create the Log Out Button

It's time to let our users log out and exit their sessions.

Register the logout view

Same as with the previous actions you need to create a view and assign the view to the URL mappings.

webapp/views.py
# ... existing code
from authlib.integrations.django_client import OAuth
from urllib.parse import quote_plus, urlencode # 👈 new code
# ... existing code
# 👇 new code
def logout(request):
request.session.clear()
return redirect(
f"https://{settings.AUTH0_DOMAIN}/v2/logout?"
+ urlencode(
{
"returnTo": request.build_absolute_uri(reverse("index")),
"client_id": settings.AUTH0_CLIENT_ID,
},
quote_via=quote_plus,
),
)
# 👆 new code
# ... existing code

Next, you need to assign a URL to the view by implementing the following definition:

webapp/urls.py
# ... existing code
urlpatterns = [
path('signup', views.signup, name='signup'),
# ... existing code
path('logout', views.logout, name='logout'), # 👈 new code
# ... existing code
path('django-admin', admin.site.urls),
]
# ... existing code

The logout view code is somewhat different from login and signup as it doesn't depend on authlib, but rather it cleans the session to log out the user from the Django application and it performs a redirect to Auth0 to log out the user from that side as well. If you don't perform the Auth0 log out, and the user presses the "Log In" button again, it won't be necessary to provide any credentials, and the authentication flow will continue automatically with the Auth0 current authenticated user.

Add the logout button to the template

Open the webapp/templates/navigation/desktop/nav_bar_buttons.html and add a new button:

webapp/templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="/signup" class="button__sign-up">Sign Up</a>
<a href="/login" class="button__login" >Log In</a>
<a href="/logout" class="button__logout">Log Out</a> <!-- 👈 new code -->
</div>

The same button needs to be added to the mobile view.

webapp/templates/navigation/mobile/mobile_nav_bar_buttons.html
<div class="mobile-nav-bar__buttons">
<a href="/signup" class="button__sign-up">Sign Up</a>
<a href="/login" class="button__login" >Log In</a>
<a href="/logout" class="button__logout">Log Out</a> <!-- 👈 new code -->
</div>
Main page with log out button on the nav bar

Render Content Based on Authentication

Because you are storing the user's ID Token in the session, you'll use this value to decide whether a user is authenticated or not.

Render Authentication buttons conditionally

Let's update the desktop view to conditionally render the buttons depending on the authentication status.

webapp/templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
{% if request.session.user %} <!-- 👈 new code -->
<a href="/logout" class="button__logout">Log Out</a>
{% else %} <!-- 👈 new code -->
<a href="/signup" class="button__sign-up">Sign Up</a>
<a href="/login" class="button__login" >Log In</a>
{% endif %} <!-- 👈 new code -->
</div>

You should apply these changes to the mobile template.

webapp/templates/navigation/mobile/mobile_nav_bar_buttons.html
<div class="nav-bar__buttons">
{% if request.session.user %} <!-- 👈 new code -->
<a href="/logout" class="button__logout">Log Out</a>
{% else %} <!-- 👈 new code -->
<a href="/signup" class="button__sign-up">Sign Up</a>
<a href="/login" class="button__login" >Log In</a>
{% endif %} <!-- 👈 new code -->
</div>

Render Navigation Tabs Conditionally

Similarly, only authenticated users are allowed to access the Protected and Admin views, so you hide the tabs from the nav bar appropriately.

webapp/templates/navigation/desktop/nav_bar_tabs.html
<div class="nav-bar__tabs">
{% include "./nav_bar_tab.html" with label="Profile" path="/profile" %}
{% include "./nav_bar_tab.html" with label="Public" path="/public" %}
{% if request.session.user %} <!-- 👈 new code -->
{% include "./nav_bar_tab.html" with label="Protected" path="/protected" %}
{% include "./nav_bar_tab.html" with label="Admin" path="/admin" %}
{% endif %} <!-- 👈 new code -->
</div>

You should apply these changes to the mobile template.

webapp/templates/navigation/mobile/mobile_nav_bar_tabs.html
<div class="nav-bar__tabs">
{% include "./nav_bar_tab.html" with label="Profile" path="/profile" %}
{% include "./nav_bar_tab.html" with label="Public" path="/public" %}
{% if request.session.user %} <!-- 👈 new code -->
{% include "./nav_bar_tab.html" with label="Protected" path="/protected" %}
{% include "./nav_bar_tab.html" with label="Admin" path="/admin" %}
{% endif %} <!-- 👈 new code -->
</div>

You should see the result of these changes reflected on your main page if no user is authenticated like so:

Main page with buttons and tabs on the nav bar being conditionally rendered for non-authenticated users

Similarly, an authenticated user would see this main page:

Main page with buttons and tabs on the nav bar being conditionally rendered for authenticated users

Secure Your Django Views

Now that the navigation tabs will render conditionally, you'll need to make sure that only authenticated users have access to certain views.

Secure the Profile, Protected and Admin views

Adding one line of code is all that is needed now to require a user to be authenticated to access a view.

webapp/views.py
# ... existing code
from urllib.parse import quote_plus, urlencode
from django.contrib.auth.decorators import login_required # 👈 new code
# ... existing code
@login_required # 👈 new code
def profile(request):
template = loader.get_template('profile/index.html')
# ... existing code
@login_required # 👈 new code
def protected(request):
template = loader.get_template('protected/index.html')
# ... existing code
@login_required # 👈 new code
def admin(request):
template = loader.get_template('admin/index.html')

The login_required decorator will be responsible to check whether or not a user is logged in by levaraging the custom Auth0 backend you declared a few steps ago. This decorator also uses the LOGIN_URL defined in webapp/settings.py to redirect users to the /login page if a given user tries to access a protected page without being logged in.

Retrieve User Profile Information

After a user successfully logs in, Auth0 sends an ID token to your Django web application. Authentication systems, such as Auth0, use ID Tokens in token-based authentication to cache user profile information and provide it to a client application. The caching of ID tokens can improve the performance and responsiveness of your Django application.

Because during the authentication routine you are storing the tokens and information processed by authlib, you have direct access to the user information from Auth0 in session.

Let's process that information in a view to build the template that shows the user information on the screen.

webapp/views.py
# ... existing code
@login_required
def profile(request):
template = loader.get_template('profile/index.html')
user = request.session.get('user').get('userinfo') # 👈 new code
# 👇 updated code
user_profile = Profile(
nickname=user.get('nickname'),
name=user.get('name'),
picture=user.get('picture'),
updated_at=user.get('updated_at'),
email=user.get('email'),
email_verified=user.get('email_verified'),
sub=user.get('sub')
)
# 👆 updated code
context = {
'user_profile': user_profile
}
return HttpResponse(template.render(context, request))
# ... existing code

Now go check the /profile page to see the user information displayed. Remember to log in.

Integrate Your Django Application with a Protected API Server

This section focuses on showing you how to get an access token in your Django Web App and how to use it to make API calls to protected API endpoints.

When you use Auth0, you delegate the authentication process to a centralized service. Auth0 provides you with functionality to log in and log out users from your Django application. However, your application may need to access protected resources from an API.

You can also protect an API with Auth0. There are multiple API quickstarts to help you integrate Auth0 with your backend platform.

When you use Auth0 to protect your API, you also delegate the authorization process to a centralized service that ensures only approved client applications can access protected resources on behalf of a user.

Retrieve an Access Token from Auth0

Thanks to authlib there's not a lot you need to do as you are already storing the information processed by the library which not only contains user information, the ID Token, but also the access token needed by the application.

To retrieve the access token you are storing in session you can simply do:

access_token = request.session.get('user').get('access_token')

How can you make secure API calls from a Django Web App?

Instead of creating an API from scratch to test the authentication and authorization flow between the client and the server, you can pair this client application with an API server that matches the technology stack you use at work. The Django "Hello World" sample application that you have been building up can interact with any of the "Hello World" API server samples from the Auth0 Developer Center.

Pick an API code sample in your preferred backend framework and language from the list below and follow the instructions on the code sample page to set it up. Once you complete the sample API server setup, please return to this page to learn how to integrate that API server with your Django application.

actix-web
rust
Actix Web/Rust API:Authorization Code Sample
Code sample of a simple Actix Web server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core v5 Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core v5.0 server that implements token-based authorization using Auth0.
django
python
Django/Python API:Authorization Code Sample
Code sample of a simple Django server that implements token-based authorization using Auth0.
express
javascript
Express.js Code Sample:Basic API Authorization
Code sample of a simple Express.js server that implements token-based authorization using Auth0.
express
typescript
Express.js/TypeScript Code Sample:Basic API Authorization
Code sample of a simple Express.js server built with TypeScript that implements token-based authorization using Auth0.
fastapi
python
FastAPI/Python Code Sample:Basic API Authorization
Code sample of a simple FastAPI server that implements token-based authorization using Auth0.
flask
python
Flask/Python API:Authorization Code Sample
Code sample of a simple Flask server that implements token-based authorization using Auth0.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 Laravel SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 Laravel SDK.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 PHP SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 PHP SDK.
lumen
php
Lumen Code Sample:Basic API Authorization
Code sample of a simple Lumen server that implements token-based authorization using Auth0.
nestjs
typescript
NestJS Code Sample:Basic API Authorization
Code sample of a simple NestJS server that implements token-based authorization using Auth0.
phoenix
elixir
Phoenix/Elixir API:Authorization Code Sample
Code sample of a simple Phoenix server that implements token-based authorization using Auth0.
rails
ruby
Ruby on Rails API:Authorization Code Sample
Code sample of a simple Rails server that implements authorization using Auth0.
spring
java
Spring Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
spring
java
Spring Functional Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, following a functional approach.
spring-webflux
java
Spring WebFlux Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring WebFlux API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
standard-library
golang
Golang Code Sample:Basic API Authorization
Code sample of a simple Golang server that implements token-based authorization using Auth0.
symfony
php
Symfony Code Sample:Basic API Authorization
Code sample of a simple Symfony server that implements token-based authorization using Auth0.

Call a Protected API from Your Django Web App

Add External API ENV Variables

Once you have set up the API server code sample, you should have created an Auth0 Audience value. Store that value in the following field so that you can use it throughout the instructions presented on this page easily:

Now, open your .env file and add the following variables to it:

.env
AUTH0_DOMAIN=AUTH0-DOMAIN
AUTH0_CLIENT_ID=AUTH0-CLIENT-ID
AUTH0_CLIENT_SECRET=AUTH0-CLIENT-SECRET
# 👇 new code
AUTH0_AUDIENCE=AUTH0-AUDIENCE
API_SERVER_URL=http://localhost:6060
# 👆 new code

You are using AUTH0_AUDIENCE to add the value of your Auth0 API Audience so that your Django web client application can request resources from the API that such audience value represents.

Let's understand better what the AUTH0_AUDIENCE and API_SERVER_URL values represent.

The API_SERVER_URL is the URL where your sample API server listens for requests. In production, you'll change this value to the URL of your live server.

Your Django application must pass an Access Token when it calls a target API to access protected resources. You can request an access token in a format that the API can verify by passing the audience during the authentication request in your login and signup views.

The value of the Auth0 Audience must be the same for both the Django web client application and the API server you decided to set up.

Why is the Auth0 Audience value the same for both apps? Auth0 uses the value of the audience prop to determine which resource server (API) the user is authorizing your Django application to access. It's like a phone number. You want to ensure that your Django application "texts the right API".

Now, let's add the audience and the server_api_url to the Django settings.

webapp/settings.py
# ...existing code
AUTH0_CLIENT_SECRET = env('AUTH0_CLIENT_SECRET')
# 👇 new code
API_SERVER_URL = env('API_SERVER_URL')
AUTH0_AUDIENCE = env('AUTH0_AUDIENCE')
# 👆 new code

Next, as already mentioned, you need to pass the audience value during the authentication request.

webapp/views.py
# ... existing code
@login_required
def login(request):
return oauth.auth0.authorize_redirect(
request,
request.build_absolute_uri(reverse("callback")),
audience=settings.AUTH0_AUDIENCE, # 👈 new code
)
# ... existing code

Update the Message Service to call an external API

Until now, the message service would have "hardcoded" message values for each of the three type of messages (public, protected, and admin).

Now you'll update the service to communicate with an external API.

The changes include change the hardcoded message for the admin endpoint for API call, and the provision of an access token during that call.

Update the webapp/services/message_service.py file accordingly:

webapp/services/message_service.py
# 👇 new code
import requests
from django.conf import settings
def make_request(path, access_token=None):
url = '{}{}'.format(settings.API_SERVER_URL, path)
if access_token is None:
headers = {}
else:
headers = {
'authorization': 'Bearer {}'.format(access_token)
}
r = requests.get(url, headers=headers)
return r.json
# 👆 new code
class MessageService():
def public_message(self):
return {
'text': 'This is a public message.'
}
def protected_message(self):
return {
'text': 'This is a protected message.'
}
# 👇 updated code
def admin_message(self, access_token):
return make_request('/api/messages/admin', access_token)
# 👆 updated code

Since your changes now affect the signature of the function admin_message to expect a parameter access_token you need to provide it during their usage.

Head on to views.py to fix those calls.

webapp/views.py
# ...existing code
@login_required
def admin(request):
template = loader.get_template('admin/index.html')
access_token = request.session.get('user').get('access_token') # 👈 new code
context = {
"message": MessageService().admin_message(access_token) # 👈 updated code
}
return HttpResponse(template.render(context, request))
# ...existing code
Getting an error on the admin page? Try logging out and logging in. Your user should request new access for the external API call.

You are getting the Access Token from the session, which you previously stored after the user authenticated in the callback method. Note you can safely use session.get('user') here because of the @login_required decorator, which guarantees the user has authenticated before accessing this view. Otherwise, the MessageService will pass a None Access Token to the API and you'll get a { message: Permission Denied } error from the External API.

Conclusion

You have implemented user authentication in Django to identify your users, get user profile information, and control the content that your users can access by protecting views and API resources.

This guide covered the most common authentication use case for a Django Web application: simple login and logout. However, Auth0 is an extensible and flexible identity platform that can help you achieve even more. If you have a more complex use case, check out the Auth0 Architecture Scenarios to learn more about the typical architecture scenarios you have identified when working with customers on implementing Auth0.

We'll cover advanced authentication patterns and tooling in future guides, such as using a pop-up instead of redirecting users to log in, adding permissions to ID tokens, using metadata to enhance user profiles, and much more.