diff --git a/Makefile b/Makefile
index 4ac218e935e8804b02bcfdebcc94cad72f17fd6c..95031ef56e155d7b0166dec760949cc4098a3d32 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@ pip = python -m $(PIP)
 poetry = python -m $(POETRY)
 venv = . $(VENV)/bin/activate &&
 
-PACKAGES = greg/ gregsite/
+PACKAGES = greg/ gregsite/ gregui/
 
 all: test
 
diff --git a/gregsite/settings/base.py b/gregsite/settings/base.py
index 606ecbb98d600948541104f1ea5893c3173dbb01..7a7642bd7348c254068be82b07b4fb9f4dcfa978 100644
--- a/gregsite/settings/base.py
+++ b/gregsite/settings/base.py
@@ -45,6 +45,7 @@ INSTALLED_APPS = [
     "drf_spectacular",
     "django_extensions",
     "django_filters",
+    "mozilla_django_oidc",
     "greg",
     "gregui",
 ]
@@ -59,11 +60,14 @@ MIDDLEWARE = [
     "sesame.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    # Refresh oicd token
+    "mozilla_django_oidc.middleware.SessionRefresh",
     "gregsite.middleware.revision_user_middleware.RevisionUserMiddleware",
 ]
 
 AUTHENTICATION_BACKENDS = [
     "django.contrib.auth.backends.ModelBackend",  # default
+    "gregui.authentication.auth_backends.GregOIDCBackend",
     "sesame.backends.ModelBackend",  # link login
 ]
 
@@ -73,8 +77,12 @@ SESSION_COOKIE_AGE = 1800  # lifetime of session in seconds
 
 CSRF_COOKIE_SAMESITE = "Strict"
 SESSION_COOKIE_SAMESITE = "Strict"
-CSRF_COOKIE_HTTPONLY = True
-SESSION_COOKIE_HTTPONLY = True
+# CSRF_COOKIE_HTTPONLY = True
+# SESSION_COOKIE_HTTPONLY = True
+
+# Enable these in production
+# CSRF_COOKIE_SECURE = True
+# SESSION_COOKIE_SECURE = True
 
 REST_FRAMEWORK = {
     "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",
@@ -145,6 +153,28 @@ AUTH_PASSWORD_VALIDATORS = [
     },
 ]
 
+# Override these in dev.py
+OIDC_RP_CLIENT_ID = ""
+OIDC_RP_CLIENT_SECRET = ""
+
+OIDC_RP_SIGN_ALGO = "RS256"
+OIDC_RP_SCOPES = "email openid userid userid-feide profile iss "
+OIDC_OP_JWKS_ENDPOINT = "https://auth.dataporten.no/openid/jwks"
+OIDC_OP_AUTHORIZATION_ENDPOINT = "https://auth.dataporten.no/oauth/authorization"
+OIDC_OP_TOKEN_ENDPOINT = "https://auth.dataporten.no/oauth/token"
+OIDC_OP_USER_ENDPOINT = "https://auth.dataporten.no/openid/userinfo"
+ALLOW_LOGOUT_GET_METHOD = True
+OIDC_END_SESSION_ENDPOINT = "https://auth.dataporten.no/openid/endsession"
+OIDC_OP_LOGOUT_URL_METHOD = "gregui.authentication.auth_backends.provider_logout"
+
+# Change these later
+LOGIN_REDIRECT_URL = "http://localhost:3000/"
+LOGOUT_REDIRECT_URL = "http://localhost:3000/"
+
+# Extra config needed since mozilla-django-oidc does not comply with point 2. and 3.
+# https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+OIDC_OP_ISSUER = "https://auth.dataporten.no"
+OIDC_TRUSTED_AUDIENCES = []
 
 # Internationalization
 # https://docs.djangoproject.com/en/3.2/topics/i18n/
diff --git a/gregsite/settings/dev.py b/gregsite/settings/dev.py
index dfaaed2ec0e1602d92ca2e6b8987b1c7339f1e11..ea13c6d0db9b25943e1b11cd585d6a34d20b8913 100644
--- a/gregsite/settings/dev.py
+++ b/gregsite/settings/dev.py
@@ -13,3 +13,18 @@ ORGREG_CLIENT = {
     "endpoints": {"base_url": "https://example.com/fake/"},
     "headers": {"X-Gravitee-Api-Key": "bar"},
 }
+
+AUTHENTICATION_BACKENDS = [
+    "gregui.authentication.auth_backends.DevBackend",  # Fake dev backend
+    "django.contrib.auth.backends.ModelBackend",  # default
+    "gregui.authentication.auth_backends.GregOIDCBackend",
+    "sesame.backends.ModelBackend",  # link login
+]
+
+LOGIN_REDIRECT_URL = "http://localhost:3000/"
+LOGOUT_REDIRECT_URL = "http://localhost:3000/"
+
+CSRF_COOKIE_SAMESITE = "Strict"
+SESSION_COOKIE_SAMESITE = "Lax"
+# CSRF_COOKIE_HTTPONLY = True
+# SESSION_COOKIE_HTTPONLY = True
diff --git a/gregsite/urls.py b/gregsite/urls.py
index e122ba1cd165eb494e7ffdd1c1830ee598ea46f3..be0f570ba76bdaa4d6263763caab7dd77684a37f 100644
--- a/gregsite/urls.py
+++ b/gregsite/urls.py
@@ -22,7 +22,8 @@ from gregui import urls as ui_urls
 admin.autodiscover()
 
 urlpatterns = [
-    path("admin/", admin.site.urls),
+    path("api/admin/", admin.site.urls),
     path("", include(greg_urls.urlpatterns)),
     path("", include(ui_urls.urlpatterns)),
+    path("api/oidc/", include("mozilla_django_oidc.urls")),
 ]
diff --git a/gregui/admin.py b/gregui/admin.py
index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..d5ea59b0fcf2cf68ccd9aeb3737e09cf4c2c2962 100644
--- a/gregui/admin.py
+++ b/gregui/admin.py
@@ -1,3 +1,11 @@
 from django.contrib import admin
+from reversion.admin import VersionAdmin
 
-# Register your models here.
+from gregui.models import GregUserProfile
+
+
+class GregUserProfileAdmin(VersionAdmin):
+    pass
+
+
+admin.site.register(GregUserProfile, GregUserProfileAdmin)
diff --git a/gregui/api/views/guest.py b/gregui/api/views/guest.py
index 4d9370203b94acd1563b9a8a1669964841721cc1..6f5011f41f669a5ea1fc90e6e9a2d279859a0212 100644
--- a/gregui/api/views/guest.py
+++ b/gregui/api/views/guest.py
@@ -4,7 +4,8 @@ from rest_framework.generics import CreateAPIView
 from greg.models import Person
 from gregui.api.serializers.guest import GuestRegisterSerializer
 
+
 class GuestRegisterView(CreateAPIView):
     queryset = Person.objects.all()
     permission_classes = [permissions.AllowAny]
-    serializer_class = GuestRegisterSerializer
\ No newline at end of file
+    serializer_class = GuestRegisterSerializer
diff --git a/gregui/api/views/userinfo.py b/gregui/api/views/userinfo.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad81b03c4b18314a2e29082ba7c0a5dc6c33aa89
--- /dev/null
+++ b/gregui/api/views/userinfo.py
@@ -0,0 +1,46 @@
+from typing import (
+    Sequence,
+    Type,
+)
+
+from rest_framework import permissions
+from rest_framework.authentication import BaseAuthentication, SessionAuthentication
+from rest_framework.permissions import BasePermission
+from rest_framework.views import APIView
+from rest_framework.response import Response
+
+from gregui.models import GregUserProfile
+
+
+class UserInfoView(APIView):
+    """
+    User info view.
+
+    Return info about the logged inn user.
+    Quick draft, we might want to expand this later.
+    """
+
+    authentication_classes: Sequence[Type[BaseAuthentication]] = [SessionAuthentication]
+    permission_classes: Sequence[Type[BasePermission]] = [permissions.IsAuthenticated]
+
+    def get(self, request, format=None):
+        user = request.user
+
+        user_profile = GregUserProfile.objects.get(user=user)
+
+        sponsor_id = None
+        person_id = None
+        if user_profile.sponsor:
+            sponsor_id = user_profile.sponsor.id
+
+        if user_profile.person:
+            person_id = user_profile.person.id
+
+        content = {
+            "feide_id": user_profile.userid_feide,
+            "name": f"{user.first_name} {user.last_name}",
+            "sponsor_id": sponsor_id,
+            "person_id": person_id,
+        }
+
+        return Response(content)
diff --git a/gregui/authentication/__init__.py b/gregui/authentication/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gregui/authentication/auth_backends.py b/gregui/authentication/auth_backends.py
new file mode 100644
index 0000000000000000000000000000000000000000..771646935630e38c3944e0c09bbb63600feb68b6
--- /dev/null
+++ b/gregui/authentication/auth_backends.py
@@ -0,0 +1,286 @@
+import logging
+import re
+import time
+
+from django.conf import settings
+from django.contrib.auth import get_user_model
+from django.contrib.auth.backends import BaseBackend
+from django.core.exceptions import SuspiciousOperation
+from mozilla_django_oidc.auth import OIDCAuthenticationBackend
+
+from greg.models import Identity, Person, Sponsor
+from gregui.models import GregUserProfile
+
+logger = logging.getLogger(__name__)
+
+
+class DevBackend(BaseBackend):
+    """
+    Development backend, no password checking.
+
+    TODO:
+    - Expand to add person/sponsor for the user
+    - How do we do login? A dev login site?
+
+    """
+
+    def __init__(self):
+        self.UserModel = get_user_model()
+
+    dev_users = {
+        "bruker1": {
+            "email": "bruker1@uio.no",
+            "first_name": "test",
+            "last_name": "testesen",
+        },
+        "bruker2": {
+            "email": "bruker2@uio.no",
+            "first_name": "foo",
+            "last_name": "foosen",
+        },
+    }
+
+    def get_userinfo(self, username):
+        return self.dev_users.get(username, {})
+
+    def get_or_create_user(self, username):
+        if not username:
+            return None
+        try:
+            user = self.UserModel.objects.get(username=username)
+        except self.UserModel.DoesNotExist:
+            userinfo = self.get_userinfo(username)
+            user = self.UserModel(username=username, **userinfo)
+            user.save()
+        return user
+
+    def authenticate(self, request, **kwargs):
+        # This has been made so that developers can authenticate as any user in test
+        username = kwargs.get("username")
+        logger.debug("Username is %s", username)
+        request.session["oidc_id_token_payload"] = {"iat": time.time()}
+        return self.get_or_create_user(username)
+
+    def get_user(self, user_id):
+        try:
+            return self.UserModel.objects.get(pk=user_id)
+        except self.UserModel.DoesNotExist:
+            return None
+
+
+# This regex is quite naive since it assumes that the person only has one last_name,
+# and everything before that is the first_name.
+NAMES_REGEX = re.compile(r"^(?P<first_name>.+) (?P<last_name>.+)$")
+ID_REGEX = re.compile(r"^(?P<id_type>[^:]+):(?P<id_value>.+)$")
+
+
+def extract_userinfo(claims):
+    name = claims["name"]
+    match = re.search(NAMES_REGEX, name)
+    first_name = match.group("first_name")
+    last_name = match.group("last_name")
+    userinfo = {
+        "email": claims["email"],
+        "first_name": first_name,
+        "last_name": last_name,
+    }
+    secondary_user_ids = claims["connect-userid_sec"]
+    for user_id in secondary_user_ids:
+        match = re.search(ID_REGEX, user_id)
+        id_type = match.group("id_type")
+        id_value = match.group("id_value")
+        if id_type == "feide":
+            userinfo["userid_feide"] = id_value
+        # TODO:
+        #  Should we also save nin of the user?
+        #  What should we do if a user has multiple feide_ids?
+    return userinfo
+
+
+class ValidatingOIDCBackend(OIDCAuthenticationBackend):
+    def validate_issuer(self, payload):
+        issuer = self.get_settings("OIDC_OP_ISSUER")
+        if not issuer == payload["iss"]:
+            raise SuspiciousOperation(
+                '"iss": %r does not match configured value for OIDC_OP_ISSUER: %r'
+                % (payload["iss"], issuer)
+            )
+
+    def validate_audience(self, payload):
+        client_id = self.get_settings("OIDC_RP_CLIENT_ID")
+        trusted_audiences = self.get_settings("OIDC_TRUSTED_AUDIENCES", [])
+        trusted_audiences = set(trusted_audiences)
+        trusted_audiences.add(client_id)
+
+        audience = payload["aud"]
+        if not isinstance(audience, list):
+            audience = [audience]
+        audience = set(audience)
+        if client_id not in audience:
+            raise SuspiciousOperation(
+                "Client id not present in audiences: %r" % audience
+            )
+        distrusted_audiences = audience.difference(trusted_audiences)
+        if distrusted_audiences:
+            raise SuspiciousOperation(
+                '"aud" contains distrusted audiences: %r' % distrusted_audiences
+            )
+
+    def validate_expiry(self, payload):
+        expire_time = payload["exp"]
+        now = time.time()
+        if now > expire_time:
+            raise SuspiciousOperation(
+                "Id-token is expired %r > %r" % (now, expire_time)
+            )
+
+    def validate_id_token(self, payload):
+        """Validate the content of the id token as required by OpenID Connect 1.0
+
+        This aims to fulfill point 2. 3. and 9. under section 3.1.3.7. ID Token
+        Validation
+        """
+        self.validate_issuer(payload)
+        self.validate_audience(payload)
+        self.validate_expiry(payload)
+        return payload
+
+    def verify_token(self, token, **kwargs):
+        # Overriding this method to fix issues described here:
+        # https://github.com/mozilla/mozilla-django-oidc/issues/340
+        payload = super().verify_token(token, **kwargs)
+        return self.validate_id_token(payload)
+
+
+class GregOIDCBackend(ValidatingOIDCBackend):
+    """
+    Custom mozilla OIDC backend for Greg.
+
+    TODO:
+    - Support for id-porten fnr login.
+    """
+
+    def verify_token(self, token, **kwargs):
+        payload = super().verify_token(token, **kwargs)
+        # While mozilla_django_oidc does have support for automatically storing the
+        # id-token, it is more useful for us to be able to store the actual payload of
+        # the id-token.
+        session = self.request.session
+        session["oidc_id_token_payload"] = {"iat": payload["iat"]}
+        return payload
+
+    def get_username(self, claims):
+        return "{}{}".format(self.get_settings("OIDC_OP_ISSUER"), claims)
+
+    def filter_users_by_claims(self, claims):
+        #  Ideally dataporten should include 'iss' in its claims, so that it can be
+        #  used along with sub to create a unique identifier for the user. However, if
+        #  the id-token has been validated, it means that the 'iss' specified by the
+        #  id-token is equal to what is specified in the configuration (OIDC_OP_ISSUER).
+        sub = claims.get("sub")
+        if not sub:
+            return self.UserModel.objects.none()
+        username = self.get_username(sub)
+
+        try:
+            user = self.UserModel.objects.filter(username=username)
+            return user
+        except self.UserModel.DoesNotExist:
+            return self.UserModel.objects.none()
+
+    def get_or_create_user(self, access_token, id_token, payload):
+        # This method has been overridden to update the user_profile object
+        # for a user at login
+        user = super().get_or_create_user(access_token, id_token, payload)
+        if user:
+            # Update or create a user_profile
+            userinfo = extract_userinfo(
+                self.get_userinfo(access_token, id_token, payload)
+            )
+            self._get_or_create_greg_user_profile(userinfo, user)
+        return user
+
+    def _get_or_create_person(self, userinfo):
+        # Update any new person info
+        person, _ = Person.objects.update_or_create(
+            first_name=userinfo["first_name"],
+            last_name=userinfo["last_name"],
+            email=userinfo["email"],
+        )
+        person.save()
+        return person
+
+    def _get_or_create_greg_user_profile(self, userinfo, user):
+        """
+        Get or create a GregUserProfile.
+
+        We try to match a logged inn user with an existing profile.
+        If no profile exists, we check if the feide_id is registered.
+
+        """
+        try:
+            user_profile = GregUserProfile.objects.get(user=user)
+        except GregUserProfile.DoesNotExist:
+
+            # Check if user is a sponsor
+            try:
+                sponsor = Sponsor.objects.get(feide_id=userinfo["userid_feide"])
+            except Sponsor.DoesNotExist:
+                sponsor = None
+
+            try:
+                # TODO, match against fnr if using id-porten.
+                identity = Identity.objects.get(
+                    type="feide_id", value=userinfo["userid_feide"]
+                )
+                person = identity.person
+            except Identity.DoesNotExist:
+                # Find or create person, and add identity
+                person = self._get_or_create_person(userinfo)
+                identity = Identity(
+                    type="feide_id", value=userinfo["userid_feide"], person=person
+                )
+                identity.save()
+
+            user_profile = GregUserProfile(
+                user=user,
+                person=person,
+                sponsor=sponsor,
+                userid_feide=userinfo["userid_feide"],
+            )
+            user_profile.save()
+
+        return user_profile
+
+    def create_user(self, claims):
+        userinfo = extract_userinfo(claims)
+        username = self.get_username(claims["sub"])
+        user = self.UserModel(
+            username=username,
+            first_name=userinfo["first_name"],
+            last_name=userinfo["last_name"],
+            email=userinfo["email"],
+        )
+        user.save()
+
+        # Create a user_profile if missing
+        self._get_or_create_greg_user_profile(userinfo, user)
+        return user
+
+    def update_user(self, user, claims):
+        username = self.get_username(claims["sub"])
+        user = self.UserModel.objects.get(username=username)
+        userinfo = extract_userinfo(claims)
+        for key, new_value in userinfo.items():
+            current_value = getattr(user, key, None)
+            if not new_value == current_value:
+                setattr(user, key, new_value)
+        user.save()
+        # Create a user_profile if missing
+        self._get_or_create_greg_user_profile(userinfo, user)
+        return user
+
+
+def provider_logout(request):
+    redirect_url = settings.OIDC_END_SESSION_ENDPOINT
+    return redirect_url
diff --git a/gregui/migrations/0001_initial.py b/gregui/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..71c6ce2c6726236ccbae48a8ac159789e5433802
--- /dev/null
+++ b/gregui/migrations/0001_initial.py
@@ -0,0 +1,70 @@
+# Generated by Django 3.2.7 on 2021-09-22 09:35
+
+import dirtyfields.dirtyfields
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ("greg", "0004_ou_deleted_active"),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="GregUserProfile",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("created", models.DateTimeField(auto_now_add=True)),
+                ("updated", models.DateTimeField(auto_now=True)),
+                (
+                    "userid_feide",
+                    models.CharField(max_length=150, verbose_name="userid-feide"),
+                ),
+                (
+                    "person",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="user_profiles",
+                        to="greg.person",
+                    ),
+                ),
+                (
+                    "sponsor",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="user_profiles",
+                        to="greg.sponsor",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+            bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
+        ),
+    ]
diff --git a/gregui/models.py b/gregui/models.py
index 71a836239075aa6e6e4ecb700e9c42c95c022d91..48b579bed9dae968c69c4e84f5ebce32171b7cd6 100644
--- a/gregui/models.py
+++ b/gregui/models.py
@@ -1,3 +1,26 @@
+from django.conf import settings
 from django.db import models
+from django.utils.translation import gettext_lazy
 
-# Create your models here.
+from greg.models import BaseModel, Person, Sponsor
+
+
+class GregUserProfile(BaseModel):
+    """Link the django user to a Person or Sponsor."""
+
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
+    person = models.ForeignKey(
+        Person,
+        on_delete=models.CASCADE,
+        related_name="user_profiles",
+        blank=True,
+        null=True,
+    )
+    sponsor = models.ForeignKey(
+        Sponsor,
+        on_delete=models.CASCADE,
+        related_name="user_profiles",
+        blank=True,
+        null=True,
+    )
+    userid_feide = models.CharField(gettext_lazy("userid-feide"), max_length=150)
diff --git a/gregui/tests/conftest.py b/gregui/tests/conftest.py
index a59673226660f89957c51362bfc0499d0b546832..d58676c39755ffdf612a2c97ce18de057888c486 100644
--- a/gregui/tests/conftest.py
+++ b/gregui/tests/conftest.py
@@ -1,20 +1,22 @@
 from rest_framework.test import APIClient
-from django.contrib.auth import get_user_model
+
+# from django.contrib.auth import get_user_model
 
 import pytest
 
-from greg.models import (
-    Consent,
-    Notification,
-    Person,
-    Sponsor,
-    SponsorOrganizationalUnit,
-    Identity,
-    Role,
-    RoleType,
-    OrganizationalUnit,
-    ConsentType,
-)
+# from greg.models import (
+#    Consent,
+#    Notification,
+#    Person,
+#    Sponsor,
+#    SponsorOrganizationalUnit,
+#    Identity,
+#    Role,
+#    RoleType,
+#    OrganizationalUnit,
+#    ConsentType,
+# )
+
 
 @pytest.fixture
 def client() -> APIClient:
diff --git a/gregui/urls.py b/gregui/urls.py
index 33070a391fb0b0cfec31461bfc2cbc5bdff826e0..062e51d697756e5c16cfc840930240d2a4ca190e 100644
--- a/gregui/urls.py
+++ b/gregui/urls.py
@@ -6,6 +6,7 @@ from django.urls.resolvers import URLResolver
 
 from gregui.api import urls as api_urls
 from gregui.views import TokenCreationView
+from gregui.api.views.userinfo import UserInfoView
 from . import views
 
 urlpatterns: List[URLResolver] = [
@@ -18,4 +19,5 @@ urlpatterns: List[URLResolver] = [
     path("api/ui/v1/session/", views.SessionView.as_view(), name="api-session"),
     path("api/ui/v1/whoami/", views.WhoAmIView.as_view(), name="api-whoami"),
     path("api/ui/v1/token/<email>", TokenCreationView.as_view()),
+    path("api/ui/v1/userinfo/", UserInfoView.as_view()),  # type: ignore
 ]
diff --git a/gregui/views.py b/gregui/views.py
index 97d2cde1f2c765bf94921719e565d568802998af..a29f809406cb85ddfe41c13df2e448c28b3db69a 100644
--- a/gregui/views.py
+++ b/gregui/views.py
@@ -91,6 +91,7 @@ class SessionView(APIView):
     permission_classes = [IsAuthenticated]
 
     @staticmethod
+    # pylint: disable=W0622
     def get(request, format=None):
         return JsonResponse({"isAuthenticated": True})
 
@@ -100,5 +101,6 @@ class WhoAmIView(APIView):
     permission_classes = [IsAuthenticated]
 
     @staticmethod
+    # pylint: disable=W0622
     def get(request, format=None):
         return JsonResponse({"username": request.user.username})
diff --git a/poetry.lock b/poetry.lock
index 4b7b8a531cd336f555f4e756d941fd0defe27f69..0943957de6dd8d05c85dd11b8282f2f0e5d02b79 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -94,6 +94,17 @@ category = "main"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "cffi"
+version = "1.14.6"
+description = "Foreign Function Interface for Python calling C code."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
 [[package]]
 name = "charset-normalizer"
 version = "2.0.4"
@@ -160,6 +171,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
 [package.extras]
 toml = ["toml"]
 
+[[package]]
+name = "cryptography"
+version = "3.4.8"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.12"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
+pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+sdist = ["setuptools-rust (>=0.11.4)"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
+
 [[package]]
 name = "decorator"
 version = "5.0.9"
@@ -453,6 +483,23 @@ MarkupSafe = ">=2.0"
 [package.extras]
 i18n = ["Babel (>=2.7)"]
 
+[[package]]
+name = "josepy"
+version = "1.9.0"
+description = "JOSE protocol implementation in Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cryptography = ">=0.8"
+PyOpenSSL = ">=0.13"
+
+[package.extras]
+dev = ["pytest", "tox"]
+docs = ["Sphinx (>=1.0)", "sphinx-rtd-theme"]
+tests = ["coverage (>=4.0)", "flake8", "mypy", "pytest-cov", "pytest-flake8 (>=0.5)", "pytest (>=2.8.0)"]
+
 [[package]]
 name = "jsonschema"
 version = "3.2.0"
@@ -513,6 +560,20 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "mozilla-django-oidc"
+version = "2.0.0"
+description = "A lightweight authentication and access management library for integration with OpenID Connect enabled authentication services."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+cryptography = "*"
+Django = ">=2.2"
+josepy = "*"
+requests = "*"
+
 [[package]]
 name = "mypy"
 version = "0.910"
@@ -697,6 +758,14 @@ category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
+[[package]]
+name = "pycparser"
+version = "2.20"
+description = "C parser in Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
 [[package]]
 name = "pydantic"
 version = "1.8.2"
@@ -763,6 +832,22 @@ python-versions = "*"
 [package.dependencies]
 pylint = ">=1.7"
 
+[[package]]
+name = "pyopenssl"
+version = "20.0.1"
+description = "Python wrapper module around the OpenSSL library"
+category = "main"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
+
+[package.dependencies]
+cryptography = ">=3.2"
+six = ">=1.5.2"
+
+[package.extras]
+docs = ["sphinx", "sphinx-rtd-theme"]
+test = ["flaky", "pretend", "pytest (>=3.0.1)"]
+
 [[package]]
 name = "pyparsing"
 version = "2.4.7"
@@ -1081,7 +1166,7 @@ python-versions = "*"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.9"
-content-hash = "4854aed8a90a9f2308063cc6e8151623de62b9bed714c49a8fde3e66e7165d86"
+content-hash = "3733c24aefbb0889d6071468bd42582689c50ce9903937f94b922997fc4a8f23"
 
 [metadata.files]
 appnope = [
@@ -1116,6 +1201,53 @@ certifi = [
     {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
     {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
 ]
+cffi = [
+    {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"},
+    {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"},
+    {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"},
+    {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"},
+    {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"},
+    {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"},
+    {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"},
+    {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"},
+    {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"},
+    {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"},
+    {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"},
+    {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"},
+    {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"},
+    {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"},
+    {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"},
+    {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"},
+    {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"},
+    {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"},
+    {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"},
+    {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"},
+    {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"},
+    {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"},
+    {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"},
+    {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"},
+    {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"},
+    {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"},
+    {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"},
+    {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"},
+    {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"},
+    {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"},
+    {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"},
+    {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"},
+    {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"},
+    {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"},
+    {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"},
+    {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"},
+    {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"},
+    {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"},
+    {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"},
+    {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"},
+    {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"},
+    {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"},
+    {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"},
+    {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"},
+    {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
+]
 charset-normalizer = [
     {file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"},
     {file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"},
@@ -1190,6 +1322,25 @@ coverage = [
     {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
     {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
 ]
+cryptography = [
+    {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"},
+    {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"},
+    {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"},
+    {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"},
+    {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"},
+    {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"},
+    {file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"},
+    {file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"},
+    {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"},
+    {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af"},
+    {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a"},
+    {file = "cryptography-3.4.8-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06"},
+    {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498"},
+    {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7"},
+    {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9"},
+    {file = "cryptography-3.4.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e"},
+    {file = "cryptography-3.4.8.tar.gz", hash = "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c"},
+]
 decorator = [
     {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"},
     {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"},
@@ -1282,6 +1433,10 @@ jinja2 = [
     {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
     {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
 ]
+josepy = [
+    {file = "josepy-1.9.0-py2.py3-none-any.whl", hash = "sha256:49798be66a467e7c81f071fe5ff03ac5e37c6d7081933612259028496bb05a68"},
+    {file = "josepy-1.9.0.tar.gz", hash = "sha256:51cce8d97ced0556aae0ce3161b26d5f0f54bc42c749d3c606edc6d97d9802dc"},
+]
 jsonschema = [
     {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
     {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
@@ -1315,12 +1470,22 @@ lockfile = [
     {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"},
 ]
 markupsafe = [
+    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
+    {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
+    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
     {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
@@ -1329,14 +1494,21 @@ markupsafe = [
     {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
+    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
     {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
+    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
     {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
@@ -1346,6 +1518,9 @@ markupsafe = [
     {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
+    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
     {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
     {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
@@ -1358,6 +1533,10 @@ mccabe = [
     {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
 ]
+mozilla-django-oidc = [
+    {file = "mozilla-django-oidc-2.0.0.tar.gz", hash = "sha256:a8b2f27c69c122d2f4d801c3759761d33debf06ae9dabbab8aed82887bba3bb8"},
+    {file = "mozilla_django_oidc-2.0.0-py2.py3-none-any.whl", hash = "sha256:53c39755b667e8c5923b1dffc3c29673198d03aa107aa42ac86b8a38b4720c25"},
+]
 mypy = [
     {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
     {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
@@ -1464,6 +1643,10 @@ py = [
     {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
     {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
 ]
+pycparser = [
+    {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
+    {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
+]
 pydantic = [
     {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
     {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
@@ -1504,6 +1687,10 @@ pylint-plugin-utils = [
     {file = "pylint-plugin-utils-0.6.tar.gz", hash = "sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a"},
     {file = "pylint_plugin_utils-0.6-py3-none-any.whl", hash = "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a"},
 ]
+pyopenssl = [
+    {file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"},
+    {file = "pyOpenSSL-20.0.1.tar.gz", hash = "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51"},
+]
 pyparsing = [
     {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
diff --git a/pyproject.toml b/pyproject.toml
index c6c6c13e52494c91b840e4bcb33d7c0527b137ef..de3569b75f655aff15beb71d8916c63c1d8db8ca 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,6 +23,7 @@ sentry-sdk = "*"
 whitenoise = "*"
 django-reversion = "*"
 django-sesame = {extras = ["ua"], version = "^2.4"}
+mozilla-django-oidc = "^2.0.0"
 
 [tool.poetry.dev-dependencies]
 Faker = "*"