Skip to content

Commit

Permalink
📝 [#99] Document the customization mechanism(s)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-maertens committed May 16, 2024
1 parent ca18850 commit eec5946
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 0 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@
Changelog
=========

0.17.0 (2024-05-??)
===================

Refactor/rewrite release.

The custom views and backend have been rewritten to be more configurable out of the box,
without needing to write much code in your own project. We've incorporated our
experiences from the Open Forms and Open Inwoner projects in this rewrite.

**💥 Breaking changes**

While we were able to perform most of the changes without breaking public API, some
aspects could not be avoided.

* The attributes ``OIDCAuthenticationBackend.sensitive_claim_names`` and
``OIDCAuthenticationBackend.config_identifier_field`` are removed. This affects you
if you were subclassing this backend to override these attributes.

You can provide these in your custom configuration model(s) as the
``oidcdb_sensitive_claims`` and ``oidcdb_username_claim`` model fields or properties. See the implementation of the ``OpenIDConnectConfigBase`` model for more details.

0.16.0 (2024-05-02)
===================

Expand Down
143 changes: 143 additions & 0 deletions docs/customizing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
=====================
Customizing behaviour
=====================

The default behaviour of mozilla-django-oidc-db (which is an extension on top of
mozilla-django-oidc, the upstream library) provides OpenID Connect configuration
to authenticate Django (admin) users. The default claim mapping and settings gravitate
towards staff users.

However, the generic mechanism of using database-backend configuration for one or more
OpenID Connect identity providers can be used much more broadly, and it doesn't even
have to manage Django user instances at all.

We offer flexibility through a generic configuration mechanism.

.. versionadded:: 0.17.0
The generic configuration mechanism was added.


Models
======

We provide an abstract base model :class:`~mozilla_django_oidc_db.models.OpenIDConnectConfigBase`.

This makes some of the upstream library settings dynamic rather than having to specify
them as Django settings. Our :class:`~mozilla_django_oidc_db.models.OpenIDConnectConfig`
model is a concrete model based on it, which provides some additional configuration
aspects, such as claim mapping, specifying the username claim...

If you want to bring your own configuration, we recommend to subclass
:class:`~mozilla_django_oidc_db.models.OpenIDConnectConfigBase` in a similar way. You
can then define model fields or properties on your own model that correspond to the
lowercased setting name, for example:

.. code-block:: python
from mozilla_django_oidc_db.fields import ClaimField
from mozilla_django_oidc_db.models import (
OpenIDConnectConfigBase,
UserInformationClaimsSources,
)
class CustomConfig(OpenIDConnectConfigBase):
oidcdb_username_claim = ClaimField(
verbose_name=_("username claim"),
default=get_default_username_claim,
help_text=_("The name of the OIDC claim that is used as the username"),
)
@property
def oidcdb_userinfo_claims_source(self):
return UserInformationClaimsSources.id_token
For different purposes/flows, you can set up different configuration models.

.. note:: In the future, we will likely change from configuration classes/models to
a single model with multiple instances, each holding their specific configuration
values.


OIDC flow initialization
========================

Typically when a user needs to authenticate, they click a button or link to do so. This
navigation is tied to a particular URL path, for example ``/auth/oidc-custom/``.

We provide :class:`~mozilla_django_oidc_db.views.OIDCInit` to point the user to a
particular configuration. With the custom model from above:

.. code-block:: python
:caption: myapp/urls.py
from django.urls import path
from mozilla_django_oidc_db.views import OIDCInit
from myapp.models import CustomConfig
urlpatterns = [
...,
path(
"auth/oidc-custom/",
OIDCInit.as_view(config_class=CustomConfig, allow_next_from_query=True),
),
...,
]
This ensures that whenever a user authenticates via the ``/auth/oidc-custom/`` URL that
throughout the whole process your custom configuration will be used.

You can also subclass this view to modify the behaviour, optionally making it the
default via the ``OIDC_AUTHENTICATE_CLASS`` setting.

Recommended override hooks
--------------------------

:meth:`mozilla_django_oidc_db.views.OIDCInit.check_idp_availability`
You can implement your own behaviour here to determine if the identity provider is
available, before the user is redirected to the authentication endpoint.

Authentication backend(s)
=========================

The authentication backend :class:`~mozilla_django_oidc_db.backends.OIDCAuthenticationBackend`
automatically picks up the configuration specified by the initialization view. Out of
box, this will either create or update a django user with the user model specified from
your settings (unless ``OIDC_CREATE_USER`` is set to ``False``).

If you want real Django users to be managed, you don't need to do anything.

However, if you want to do more advanced stuff (like only storing certain claims in the
django session), you can subclass our backend to modify the behaviour. Don't forget
to add this backend to the ``AUTHENTICATION_BACKENDS`` setting.

Recommended override hooks
--------------------------

:meth:`mozilla_django_oidc_db.backends.OIDCAuthenticationBackend.get_or_create_user`
Override this method if you only want to extract some information and persist it
somewhere else.

You can return an :class:`~django.contrib.auth.models.AnonymousUser` instance to
signal successful authentication.

:meth:`mozilla_django_oidc_db.backends.OIDCAuthenticationBackend._check_candidate_backend`
Based on ``self.config_class``, you can determine if this backend is relevant for
your authentication purposes. If you return ``False``, the backend will be skipped
and the next one in ``AUTHENTICATION_BACKENDS`` will be tried.

``self.config_class`` will be the model specified in the init flow.


Callback flow
=============

:class:`~mozilla_django_oidc_db.views.OIDCCallbackView` takes care of preparing the
request for the authentication backend(s). You can provide a different class to
implement your own error handling, for example.

.. todo:: refactor to (abstract) base and concrete class
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Using ``email`` as the unique identifier is not recommended, as mentioned in the
:caption: Contents:

quickstart
customizing
reference
changelog

Expand Down
6 changes: 6 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Views
:members:


Authentication backends
=======================

.. automodule:: mozilla_django_oidc_db.backends
:members:

Utils
=====

Expand Down

0 comments on commit eec5946

Please sign in to comment.