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.