Authentification avancée dans Django Ninja

31 mars 2023 16:51 dans data-pipelines / publications

Django offre des fonctionnalités puissantes pour authentifier et différencier les utilisateurs. Nous allons voir comment implémenter une authentification basée sur des groupes dans Django Ninja. Scénario: nous cherchons a vérifier si request.user est membre d'un groupe dont le nom est fourni dans un header http custom par le frontend. Créons…

Slider Image

Authentification avancée dans Django Ninja

Django offre des fonctionnalités puissantes pour authentifier et différencier les utilisateurs. Nous allons voir comment implémenter une authentification basée sur des groupes dans Django Ninja.

Scénario: nous cherchons a vérifier si request.user est membre d'un groupe dont le nom est fourni dans un header http custom par le frontend.

Créons un fichier api_auth.py dans l'app principale à coté de api.py

Exceptions

Commençons par déclarer quelques exceptions custom


      class NoGroupProvidedInHeader(Exception):
          pass

      class IsNotMemberOfGroup(Exception):
          pass         
    

Logique d'authentification

Avant d'utiliser ces exceptions dans nos classes d'authentification spécifiques, nous allons préparer une fonction contenant la logique d'authentification


      from django.contrib.auth.models import AbstractBaseUser

      def is_member_of_group(user: AbstractBaseUser, group_slug: str) -> bool:
          has_perm = user.groups.filter(name=group_slug).exists()
          if has_perm:
              return True
            return False
    

Classes d'authentification

Utilisons maintenant ces éléments pour déclarer nos classes d'authentification


      from django.http import HttpRequest
      from ninja.security import APIKeyHeader
      
      class IsMemberOfGroup(APIKeyHeader):
          param_name = "X-GROUP-Slug"
      
          def authenticate(self, request: HttpRequest, key: str) -> str:
              if request.user.is_superuser is True:
                  return key
              if request.user.is_authenticated is True:
                  if not key:
                      raise NoGroupProvidedInHeader()
                  if is_member_of_group(request.user, key):
                      return key
              raise IsNotMemberOfGroup()
      
      
      auth_group = IsMemberOfGroup()
    

A noter la propriété param_name = "X-GROUP-Slug" et l'héritage de APIKeyHeader permet de déclarer un header http custom qui sera requis pour chaque request. Nous détaillerons ceci plus bas, voyons d'abord comment brancher cela.

Nous pouvons utiliser cette classe d'authentification au niveau router ou au niveau endpoint. Connectons cette authentification sur un endpoint en utilisant le décorateur


      from myapp.api_auth import auth_group

      @router.get(
          "/some/group/specific/url",
          response={ 204: None, 401: None },
          auth=auth_group
      )
    

Http custom header

Ce endpoint va ainsi exiger un http header X-GROUP-Slug envoyé par le fontend et contenant le nom du groupe dont le user doit être membre

Settings Django

Nous devons déclarer notre custom header dans Django dans les paramètres cors (package django-cors-headers):


      from corsheaders.defaults import default_headers
      
      CORS_ALLOW_HEADERS = list(default_headers) + [
          "X-GROUP-Slug",
      ]
    

Utiliser la valeur du header dans le endpoint

Il est possible de récupérer la valeur du header http custom dans un endpoint de la manière suivante


      def my_group_endpoint(
          request: HttpRequest,
      ):
          # get the value of the X-GROUP-Slug header
          group_slug: str = request.auth
    

Api doc

Pour faire fonctionner correctement l'api doc avec un custom header, ajouter un paramètre à la définition de la fonction pour déclarer une variable faisant référence au custom header


      from ninja import Router, Header
      
      def my_group_endpoint(
          request: HttpRequest,
          x_group: str | None = Header(alias="X-GROUP-Slug", default="mygroup"),
      ):
          // ...
    

De cette manière l'api doc fournira un champ x_group pour renseigner la valeur du header

Conclusion

L'api d'authentification de Django Ninja apparait suffisamment souple pour implémenter des scénarios d'authentification complexes, d'autant que la relative simplicité du code représente un atout non négligeable pour la maintenance à long terme.