Django Authentication By Example
Published on June 26, 2024This 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:
Once you clone the repo, make web-app_django_python_hello-world
your current directory:
cd web-app_django_python_hello-world
Next, to work with the project you will create a virtual environment:
python3 -m venv venv
Activate the virtual environment:
source venv/bin/activate
Install the project dependencies as follows:
pip install -U pip && pip install -r requirements.txt
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 DjangoThis 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:
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:
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:
export DJANGO_SECRET_KEY=<generated-key>
Once that key is exported you can execute this command to run your Django application:
python manage.py runserver 4040
You'll see the following output on the command line:
Watching for file changes with StatReloaderPerforming system checks...System check identified no issues (0 silenced).June 11, 2024 - 14:35:37Django 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:
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:
- Auth0 Universal Login for Web, iOS & Android.
- Up to 2 social identity providers like Google, GitHub, and Twitter.
- Up to 3 Actions, Rules, & Hooks to customize and extend Auth0's capabilities.
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:
Auth0 Django Web Application
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:
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.
http://127.0.0.1:4040
The above value is the URL that Auth0 can use to redirect your users after they log out.
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:
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:
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:
touch .env
Now, update the .env
you just created by adding the following:
AUTH0_DOMAIN=AUTH0-DOMAINAUTH0_CLIENT_ID=AUTH0-CLIENT-IDAUTH0_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:
django ~= 5.0.3requests ~= 2.31.0django-environ ~= 0.11.2authlib ~= 1.3.0
You should stop your server if it is still running, then install the libraries like so:
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:
import environ # 👈 new codeimport osfrom pathlib import Path# 👇 new codeenv = environ.Env(# set casting, default valueDEBUG=(bool, True))# 👆 new code# Build paths inside the project like this: BASE_DIR / 'subdir'.BASE_DIR = Path(__file__).resolve().parent.parentTEMPLATE_DIR = os.path.join(BASE_DIR, "webapp", "templates")# 👇 new code# Take environment variables from .env fileenviron.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 SettingdAUTH0_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
?
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:
from django.http import HttpResponsefrom django.template import loader# 👇 new codefrom django.conf import settingsfrom authlib.integrations.django_client import OAuth# 👆 new codefrom .models import Profilefrom .services.message_service import MessageService# 👇 new codeoauth = 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
?
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 asname
,nickname
, andpicture
. -
email
: This scope value requests access to theemail
andemail_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:
touch webapp/auth0_backend.py
Then populate it with the following content:
from django.contrib.auth.backends import BaseBackendfrom django.contrib.auth.models import Userclass Auth0Backend(BaseBackend):def authenticate(self, request, token=None):request.session["user"] = tokenuser_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 dashusername = 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 databaseuser = User(username=username)user.save()return userdef 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.
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.
# ...existing codeAUTHENTICATION_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.
# ... existing codefrom django.conf import settings# 👇 new codefrom django.shortcuts import redirectfrom django.urls import reversefrom django.contrib import auth# 👆 new codefrom authlib.integrations.django_client import OAuth# ... existing codeoauth = 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 codedef 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.
# ... existing codefrom . import views # 👈 new codeurlpatterns = [path('admin', views.admin, name='admin'),# ... existing codepath('callback', views.callback, name='callback'), # 👈 new code# ... existing codepath('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:
# ... existing code# 👇 new codedef 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:
# ... existing codeurlpatterns = [path('callback', views.callback, name='callback'),# ... existing codepath('login', views.login, name='login'), # 👈 new code# ... existing codepath('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:
touch webapp/templates/navigation/desktop/nav_bar_buttons.html
Next, fill it up with the following content:
<div class="nav-bar__buttons"><a href="/login" class="button__login" >Log In</a></div>
And create an additional file for the mobile view:
touch webapp/templates/navigation/mobile/mobile_nav_bar_buttons.html
Next, fill it up with the following content:
<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:
<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:
<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:
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.
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.
# ... existing code# 👇 new codedef 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:
# ... existing codeurlpatterns = [path('login', views.login, name='login'),# ... existing codepath('signup', views.signup, name='signup'), # 👈 new code# ... existing codepath('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:
Add the sign up button to the template
Open the webapp/templates/navigation/desktop/nav_bar_buttons.html
and add a new button:
<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:
<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.
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.
# ... existing codefrom authlib.integrations.django_client import OAuthfrom urllib.parse import quote_plus, urlencode # 👈 new code# ... existing code# 👇 new codedef 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:
# ... existing codeurlpatterns = [path('signup', views.signup, name='signup'),# ... existing codepath('logout', views.logout, name='logout'), # 👈 new code# ... existing codepath('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:
<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.
<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>
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.
<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.
<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.
<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.
<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:
Similarly, an authenticated user would see this main page:
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.
# ... existing codefrom urllib.parse import quote_plus, urlencodefrom django.contrib.auth.decorators import login_required # 👈 new code# ... existing code@login_required # 👈 new codedef profile(request):template = loader.get_template('profile/index.html')# ... existing code@login_required # 👈 new codedef protected(request):template = loader.get_template('protected/index.html')# ... existing code@login_required # 👈 new codedef 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.
# ... existing code@login_requireddef profile(request):template = loader.get_template('profile/index.html')user = request.session.get('user').get('userinfo') # 👈 new code# 👇 updated codeuser_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 codecontext = {'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.
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:
AUTH0_DOMAIN=AUTH0-DOMAINAUTH0_CLIENT_ID=AUTH0-CLIENT-IDAUTH0_CLIENT_SECRET=AUTH0-CLIENT-SECRET# 👇 new codeAUTH0_AUDIENCE=AUTH0-AUDIENCEAPI_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.
# ...existing codeAUTH0_CLIENT_SECRET = env('AUTH0_CLIENT_SECRET')# 👇 new codeAPI_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.
# ... existing code@login_requireddef 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:
# 👇 new codeimport requestsfrom django.conf import settingsdef 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 codeclass MessageService():def public_message(self):return {'text': 'This is a public message.'}def protected_message(self):return {'text': 'This is a protected message.'}# 👇 updated codedef 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.
# ...existing code@login_requireddef admin(request):template = loader.get_template('admin/index.html')access_token = request.session.get('user').get('access_token') # 👈 new codecontext = {"message": MessageService().admin_message(access_token) # 👈 updated code}return HttpResponse(template.render(context, request))# ...existing code
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.