Install

Python modules

Required modules:

requirements.txt
--extra-index-url https://git.fs.tum.de/api/v4/groups/django/-/packages/pypi/simple
django-compref-keycloak<2.0.0

Include these lines in your requirements.txt. The version pinning allows us to publish a version with breaking changes in the future without breaking all the applications.

URLs

Include the URLs from mozilla-django-oidc:

from django.views.generic.base import TemplateView

urlpatterns = [
    ...
    path('oidc/', include('mozilla_django_oidc.urls')),

    path("login/", TemplateView.as_view(template_name="appname/login.html")),  # probably already there
    path("login/failed/", TemplateView.as_view(template_name="appname/login_failed.html")),  # adapt to your app
    ...
]

Template for login pages

Build a login page that describes who can login and contains the link to Keycloak:

appname/login.html
<a href="{% url 'oidc_authentication_init' %}">Login</a>

And a page that is displayed in case the login is not allowed (for example is the user is federated from an IDP that you do not allow):

appname/login_failed.html
<p>You are not allowed to login to the application or your session is expired (feel free to write a more useful error message here).</p>

<a href="/">Back to application</a>

Template for logout buttons

The logout should be done in Django and in the OpenID Connect provider. Otherwise, users think that they are logged out, but the Keycloak session is still active.

If you only have users from Keycloak, this is a simple template:

{% if user.is_authenticated %}
    <a href="{% url "oidc_logout" %}">Logout</a>
{% endif %}

If you want to support local users, you should have different logout URLs:

{% if user.is_authenticated %}
    {% if user.has_usable_password %}
       <a href="{% url "logout" %}">Logout</a>
    {% else %}
       <a href="{% url "oidc_logout" %}">Logout</a>
    {% endif %}
{% endif %}

Settings

Several settings need to be configured.

The login page must be configured (probably already there for existing projects). Change the URLs are required for your app, for example:

LOGIN_URL = "/login/"
LOGIN_REDIRECT_URL="/"
LOGOUT_REDIRECT_URL="/"
LOGIN_REDIRECT_URL_FAILURE = "/login/failed"

mozilla_django_oidc and django_compref_keycloak must be added to the INSTALLED_APPS somewhere after django.contrib.auth and before own apps:

INSTALLED_APPS = [
    'django.contrib.auth',
    ...
    'mozilla_django_oidc',
    'django_compref_keycloak',
    ...,
    'myapp',
]

Configure django_compref_keycloak.backend.CompRefKeycloakAuthenticationBackend as first authentication backend.

AUTHENTICATION_BACKENDS = (
    'django_compref_keycloak.backend.CompRefKeycloakAuthenticationBackend',
    'django.contrib.auth.backends.ModelBackend',  # not necessary, you can remove it
    ...
)

Add mozilla_django_oidc.middleware.SessionRefresh middleware after every other middleware involving session and authentication:

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ...
    'mozilla_django_oidc.middleware.SessionRefresh',
]

The following settings belong to the mozilla-django-oidc module and configure the basic connectivity to our Keycloak.

# realm is either federated-tum.de or fs.tum.de - provided by CompRef
OIDC_OP_AUTHORIZATION_ENDPOINT = "https://auth.fs.tum.de/auth/realms/federated-tum.de/protocol/openid-connect/auth"
OIDC_OP_TOKEN_ENDPOINT = "https://auth.fs.tum.de/auth/realms/federated-tum.de/protocol/openid-connect/token"
OIDC_OP_USER_ENDPOINT = "https://auth.fs.tum.de/auth/realms/federated-tum.de/protocol/openid-connect/userinfo"
OIDC_OP_JWKS_ENDPOINT = "https://auth.fs.tum.de/auth/realms/federated-tum.de/protocol/openid-connect/certs"

# We have OpenID Connect clients configured for testing.
# It is okay to put their secrets to internal git repositories, but not to public ones!
# These test clients only accept http://localhost:8000 as redirect URL.
OIDC_RP_CLIENT_ID = "..."  # provided by CompRef and overwritten for prod deployment
OIDC_RP_CLIENT_SECRET = "..."  # provided by CompRef and overwritten for prod deployment

OIDC_RP_SIGN_ALGO = "RS256"
OIDC_STORE_ID_TOKEN = True
ALLOW_LOGOUT_GET_METHOD = True
OIDC_USERNAME_ALGO = "django_compref_keycloak.backend.generate_username"
OIDC_OP_LOGOUT_URL_METHOD = "django_compref_keycloak.backend.logout_url"

Finally, we need to configure who is allowed to log in and which privileges should be assigned to these users.

# Allowed federated identity providers
# fs.tum.de accounts
COMPREF_KEYCLOAK_FEDERATED_IDP = {
   # for fs.tum.de realm
   "fs.tum.de-internal": {
      # allow login with fs.tum.de account?
      "enabled": True,
      # restrict login to LDAP groups (empty = allow everyone)
      "active_groups": ["compref", "set"],
      # grant superuser privileges for these LDAP groups (empty = is_staff is not touched, also not removed!)
      "staff_groups": ["compref"],
      # grant superuser privileges for these LDAP groups (empty = is_superuser is not touched, also not removed!)
      "superuser_groups": ["compref"],
      # sync LDAP groups to Django groups
      "sync_groups": True,
   },
   # for federated-tum.de realm
   "fs.tum.de": {
      # allow login with fs.tum.de account?
      "enabled": True,
      # restrict login to LDAP groups (empty = allow everyone)
      "active_groups": [],
      # grant superuser privileges for these LDAP groups (empty = is_staff is not touched, also not removed!)
      "staff_groups": ["users"],
      # grant superuser privileges for these LDAP groups (empty = is_superuser is not touched, also not removed!)
      "superuser_groups": ["users"],
      # sync LDAP groups to Django groups
      "sync_groups": True,
   },
   "shibboleth.tum.de": {
      "enabled": True,  # allow login with TUM account?
      "active": {
         # affiliations: student, alum, employee (more values possible - these are the important ones)
         # empty = allow everyone and following settings are ignored!
         "affiliations": ["student", "employee"],

         # we can restrict the school for students (empty = allow every student)
         # only checked if "student" is included in affiliations
         "org_student": ["TUINFIN"],

         # we can restrict the school for employees (empty = allow every employee)
         # only checked if "employee" is included in affiliations
         "org_employee": [],
      }
      # note: is_staff/superuser are not touched (= also not set to False)
   }
}

Data for Keycloak (for CompRef)

Callback URLs:

  • https://app.fs.tum.de/oidc/callback/ (for login)

  • https://app.fs.tum.de/ (redirect after logout)