Django Web Authentication with Keycloak
Tags: django, keycloak, authentication, sso
Published: 2023-09-04 14:50:13

---
canonical_url: https://phasetwo.io/blog/secure-django
---

[Django](https://www.djangoproject.com/) is a high-level, open-source web framework for building web applications using the Python programming language. It follows the Model-View-Controller (MVC) architectural pattern.

In this article we'll be using [Keycloak](https://www.keycloak.org/) to secure a Django Web application.

{% cta https://phasetwo.io/ %} Phase Two {% endcta %} is a [Keycloak as a Service](https://phasetwo.io/) provider enabling SaaS builders to accelerate time-to-market with powerful enterprise features like SSO, identity, and user management features. Phase Two enhances Keycloak through a variety of open-source extentions for modern SaaS use cases. Phase Two supports both hosted and on-premise deployment options. 

_What is Keycloak?_

[Keycloak](https://www.keycloak.org/) has been a leader in the Identity and Access Management world since its launch almost 8 years ago. It is an open-source offering under the stewardship of [Red Hat](https://www.redhat.com/)

> **INFO**
If you just want to skip to the code, visit the Phase Two [Django example](https://github.com/p2-inc/examples/tree/main/frameworks/django). We are also building [Keycloak examples](https://github.com/p2-inc/examples) for other frameworks.


## TOC

* [Django Project Setup](#django-setup)
* [Keycloak Setup](#keycloak-setup)
* [Django OIDC + Keycloak Integration](#django-oidc)

---

## Setting up a Django Project <a name="django-setup"></a>

The following could be applied to an existing Django application, but we have chosen to use the excellent tutorial application built by [Mozilla](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django) as our example. If you aren't yet familiar with Django, we encourage you to follow the tutorial there.

The completed code for that tutorial is available in their GitHub repository. We'll clone it to get started.

### Quick Start

To get this project up and running locally on your computer:
1. Set up the [Python development environment](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/development_environment).
   We recommend using a Python virtual environment.
1. Assuming you have Python setup, run the following commands (if you're on Windows you may use `py` or `py -3` instead of `python` to start Python):
   ```
   pip install -r requirements.txt
   python manage.py makemigrations
   python manage.py migrate
   python manage.py collectstatic
   python manage.py test # Run the standard tests. These should all pass.
   python manage.py createsuperuser # Create a superuser
   python manage.py runserver
   ```
1. Open a browser to `http://127.0.0.1:8000/admin/` to open the admin site
1. Create a few test objects of each type.
1. Open tab to `http://127.0.0.1:8000` to see the main site, with your new objects.

---

## Setting up a Keycloak Instance <a name="keycloak-setup"></a>

> **TIP**
If you already have a functioning Keycloak instance, you can skip to the next section.

{% details **Keycloak Setup Details** %} 
Rather than trying to set up a "from scratch" instance of Keycloak, we're going to short-circuit that process by leveraging a Phase Two [free Keycloak](https://phasetwo.io/) starter instance. The Starter provides a free hosted instance of Phase Two's enhanced Keycloak ready for light production use cases.

- Visit the sign-up [page](https://phasetwo.io/dashboard/).
- Enter an email, use a Github account, or use an existing Google account to register.

![Register](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1jovhdjmmnys3kjp1wb.png)

- Follow the register steps. This will include a sign-in link being sent to your email. Use that for password-less login.

![Email Link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s73nhw4jl43li164nm5i.png)

- After creating an account, a [realm](https://www.youtube.com/watch?v=ZTFlc-3pG1M) is automatically created for you with all of the Phase Two enhancements. You need to create a Deployment in the Shared Phase Two infrastructure in order to gain access to the realm. Without a deployment created, the Create Shared Deployment modal will automatically pop up.
- Create a Shared Deployment by providing a region (pick something close to your existing infrastructure), a name for the deployment, and selecting the default organization that was created for you upon account creation. Hit "Confirm" when ready. Standby while our robots get to work generating your deployment. This can take a few seconds.

  ![Create shared deployment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7oknshhzra67byi0uxoz.png)

- After the deployment is created and active, you can access the Keycloak Admin console by clicking "Open Console" for that deployment. Open it now to see the console.

![Deployments](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arwn5n54hbzwocobkmzs.png)

At this point, move on to the next step in the tutorial. We'll be coming back to the Admin Console when its time to start connecting our App to the Keycloak instance.
{% enddetails %}


## Setting up an OIDC Client

We need to create a [OpenID Connect](https://www.keycloak.org/docs/latest/server_admin/#con-oidc_server_administration_guide) Client in Keycloak for the app to communicate with. 

{% details **Details** %} 

Keycloak's [docs](https://www.keycloak.org/docs/latest/server_admin/#_oidc_clients) provide steps for how to create an OIDC client and all the various configurations that can be introduced. Follow the steps below to create a client and get the right information necessary for app configuration.

1.  Open the Admin UI by clicking **Open Console** in the Phase Two Dashboard.
1.  Click **Clients** in the menu.
1.  Click **Create client**.
1.  Leave **Client type** set to **OpenID Connect**.
1.  Enter a **Client ID**. This ID is an alphanumeric string that is used in OIDC requests and in the Keycloak database to identify the client.
1.  Supply a **Name** for the client.
1.  Click **Next**.
![General settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vmlbu0gbf3wdmmacy23.png)
1.  Under the Capability Config section, leave the defaults as selected. This can be configured further later.
- Client authentication to On.
- Authorization to Off.
- Standard flow checked. Direct access grants checked. All other items unchecked.
1.  Click **Next**.
![Capbility config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qrgqmz8b4chnztrs8d8.png)
1.  Under Login settings we need to add a redirect URI and Web origin in order. Assuming you are using the example application:
**URI and Origin Details**
The choice of `localhost` is arbitrary. If you are using an example application running locally, this will apply. If you are using an app that you actually have deployed somewhere, then you will need to substitute the appropriate URI for that.

    _Valid redirect URI_ (allows redirect back to application)
    ```
    http://localhost:3000/*
    ```
    _Web origins_ (allows for Token auth call)
    ```
    http://localhost:3000
    ```

1.  Click **Save**
![Login settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lk6j7sjbv2b5ns7jc85a.png)
{% enddetails %}

### OIDC Config

{% details **Details** %}
We will need values to configure our application. To get these values follow the instructions below.

1. Click **Clients** in the menu.
1. Find the Client you just created and click on it. In the top right click the **Action** dropdown and select **Download adapter config**.
1. Select **Keycloak OIDC JSON** in the format option. The details section will populate with the details we will need.
   - Note the `realm`, `auth-server-url`, and `resource` values.
![Adapter config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjxmpf25hhyogdno2yht.png)
1. You also need to copy the **Client secret** in the **Credential** tab for the client to use. Once on the Credential tab, click the copy button to copy the key to your clipboard. Save the key somewhere for use later in this tutorial
![Copy Credential](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/482rc92jw9h6ydmo8zvv.png)
{% enddetails %}

## Adding a Non-Admin User

> **INFO**
It is bad practice to use your Admin user to sign in to an Application.

Since we do not want to use our Admin user for signing into the app we will build, we need to add another non-admin user.

{% details **Details** %}
1.  Open the Admin UI by clicking **Open Console** in the Phase Two Dashboard.
1.  Click **Users** in the menu.
1.  Click **Add user**.
1.  Fill out the information for Email, First name, and Last name. Click **Create**.
1.  We will now set the password for this user manually. Click **Credentials** (tab) and click **Set Password**. Provide a password for this user. For our use case, as a tutorial, you can leave "Temporary" set to "Off".
1.  Click **Save** and confirm the password by clicking **Save password**
{% enddetails %}

---

## Install and configure the Django OIDC library <a name="django-oidc"></a>

Now that we've installed and configured Keycloak, we need to setup Django to replace the native authentication method provided by the framework. The first task is to install a library that is compatible with Keycloak's OIDC implementation.

The [mozilla-django-oidc](https://mozilla-django-oidc.readthedocs.io/) library provides an easy way to integrate Keycloak (or any OpenID Connect-compliant identity provider) with your Django app. It abstracts many of the complexities of integrating authentication and authorization. Here's how you can set it up:

1. **Install the Package**:
   Install the `mozilla-django-oidc` package using pip:

   ```bash
   pip install mozilla-django-oidc
   ```

2. **Configure Django Settings**:
   Update your Django app's `settings.py` to include the necessary configurations for `mozilla-django-oidc`:

   ```python
   INSTALLED_APPS = [
       # ...
       'django.contrib.auth',
       'mozilla_django_oidc',  # Load after django.contrib.auth
       # ...
   ]

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

   OIDC_RP_CLIENT_ID = 'your-client-id'
   OIDC_RP_CLIENT_SECRET = 'your-client-secret'
   OIDC_OP_AUTHORIZATION_ENDPOINT = 'https://keycloak-url/auth/realms/your-realm/protocol/openid-connect/auth'
   OIDC_OP_TOKEN_ENDPOINT = 'https://keycloak-url/auth/realms/your-realm/protocol/openid-connect/token'
   OIDC_OP_USER_ENDPOINT = 'https://keycloak-url/auth/realms/your-realm/protocol/openid-connect/userinfo'
   OIDC_OP_JWKS_ENDPOINT = 'https://keycloak-url/auth/realms/your-realm/protocol/openid-connect/certs'
   OIDC_RP_SIGN_ALGO = 'RS256'

   LOGIN_URL = 'oidc_authentication_init'
   LOGOUT_REDIRECT_URL = '/'
   LOGIN_REDIRECT_URL = '/'
   ```

   Replace `your-client-id`, `your-client-secret`, and the Keycloak URLs with your actual Keycloak configurations.

3. **Add URLs**:
   Update your Django app's `urls.py` to include the authentication URLs provided by `mozilla-django-oidc`:

   ```python
   urlpatterns += [
       path('oidc/', include('mozilla_django_oidc.urls')),
   ]
   ```

## Using it in your app

### Protect your views

Use Decorators for Access Control. You can now use the `@oidc_protected` decorator to protect views that require authentication and potentially specific roles:

```python
from mozilla_django_oidc.decorators import oidc_protected

@oidc_protected
def protected_view(request):
    # Your view logic
```

### Accessing user information

You can access user information after authentication using the `request.oidc_user` attribute. For example:

```python
def profile_view(request):
    user_info = request.oidc_user.userinfo
    # Access user_info['sub'], user_info['email'], etc.
    # Your view logic
```

By default, `mozilla-django-oidc` looks up a Django user matching the email field to the email address returned in the user info data from Keycloak.

If a user logs into your site and doesn’t already have an account, by default, `mozilla-django-oidc` will create a new Django user account. It will create the User instance filling in the username (hash of the email address) and email fields.

#### Use Username rather than Email

`mozilla-django-oidc` defaults to setting up Django users using the email address as the user name from keycloak was required. Fortunately, `preferred_username` is set up by default in Keycloak as a claim. The claim can used by overriding the `OIDCAuthenticationBackend` class in `mozilla_django_oidc.auth` and referring to this in `AUTHENTICATION_BACKENDS` as below:

```python

# Classes to override default OIDCAuthenticationBackend (Keycloak authentication)
from mozilla_django_oidc.auth import OIDCAuthenticationBackend

class KeycloakOIDCAuthenticationBackend(OIDCAuthenticationBackend):

 def create_user(self, claims):
     """ Overrides Authentication Backend so that Django users are
         created with the keycloak preferred_username.
         If nothing found matching the email, then try the username.
     """
     user = super(KeycloakOIDCAuthenticationBackend, self).create_user(claims)
     user.first_name = claims.get('given_name', '')
     user.last_name = claims.get('family_name', '')
     user.email = claims.get('email')
     user.username = claims.get('preferred_username')
     user.save()
     return user

 def filter_users_by_claims(self, claims):
     """ Return all users matching the specified email.
         If nothing found matching the email, then try the username
     """
     email = claims.get('email')
     preferred_username = claims.get('preferred_username')

     if not email:
         return self.UserModel.objects.none()
     users = self.UserModel.objects.filter(email__iexact=email)

     if len(users) < 1:
         if not preferred_username:
             return self.UserModel.objects.none()
         users = self.UserModel.objects.filter(username__iexact=preferred_username)
     return users

 def update_user(self, user, claims):
     user.first_name = claims.get('given_name', '')
     user.last_name = claims.get('family_name', '')
     user.email = claims.get('email')
     user.username = claims.get('preferred_username')
     user.save()
     return user
```

In settings.py, overide the new library you have just added in AUTHENTICATION_BACKENDS :

```python
 # mozilla_django_oidc - Keycloak authentication
 "fragalysis.auth.KeycloakOIDCAuthenticationBackend",
```

### Logging out

You can use the `@oidc_logout` decorator to log the user out of both your app and Keycloak:

```python
from mozilla_django_oidc.decorators import oidc_logout

@oidc_logout
def logout_view(request):
    # Your logout view logic
```

## Add support for Django Rest Framework

Django Rest Framework (DRF) is a flexible toolkit built on top of Django, specifically designed for building RESTful APIs.

If you want DRF to authenticate users based on an OAuth access token provided in the Authorization header, you can use the DRF-specific authentication class which ships with the package.

Add this to your settings:

```python
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'mozilla_django_oidc.contrib.drf.OIDCAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        # other authentication classes, if needed
    ],
}
```

Note that this only takes care of authenticating against an access token, and provides no options to create or renew tokens.

If you’ve created a custom Django OIDCAuthenticationBackend and added that to your AUTHENTICATION_BACKENDS, the DRF class should be smart enough to figure that out. Alternatively, you can manually set the OIDC backend to use:

```python
OIDC_DRF_AUTH_BACKEND = 'mozilla_django_oidc.auth.OIDCAuthenticationBackend'
```

## Learning more

Phase Two's enhanced Keycloak provides many ways to quickly control and tweak the log in and user management experience. Our [blog](https://phasetwo.io/blog) has many use cases from [customizing login pages](https://phasetwo.io/blog/customizing-login-pages), setting up [magic links](https://phasetwo.io/blog/set-up-magic-links) (password-less sign in), and [Organization](https://phasetwo.io/product/organizations) workflows.