Application Django réutilisable d'authentification (JWT + OTP)
Project description
forge-auth
Application Django réutilisable fournissant un système d'authentification complet : utilisateur personnalisé, connexion par mot de passe ou par code OTP (one-time password), JWT (header ou cookie httponly), gestion de groupes/permissions et endpoints REST prêts à l'emploi (Django REST Framework).
Sommaire
- Fonctionnalités
- Installation
- Configuration rapide
- Référence complète des options
FORGE_AUTH - Scénarios de configuration détaillés
- Endpoints de l'API
- Exemples d'utilisation
- Modèle
User: méthodes et propriétés utiles - Avertissement sur les migrations
- Points non automatisés (à implémenter côté projet hôte)
- Notes de sécurité
- Lancer les tests
Fonctionnalités
- Modèle
Userpersonnalisé sans champusernameimposé : authentification parphone_number,email, ou les deux. - Champs
status(vérification de compte) etotp_secret(TOTP) optionnels et désactivables. - Authentification par mot de passe ou par code OTP, au choix.
- JWT via header
Authorization: Bearerou via cookies httponly, au choix (les deux peuvent être actifs simultanément). - Backend d'authentification Django supportant plusieurs champs de connexion (
MultiFieldBackend). - ViewSets DRF prêts à l'emploi : inscription, connexion, déconnexion, rafraîchissement de token, vérification d'unicité email/téléphone, utilisateur courant, vérification de session.
- Documentation OpenAPI via
drf-spectacular(extend_schemadéjà posé sur chaque action). - Validation de configuration au démarrage (
AppConfig.ready()), qui stoppe le serveur siFORGE_AUTHest mal formé.
Installation
Le package est structuré en layout src/ et se construit avec hatchling. Avec uv, depuis le projet Django qui consomme forge-auth :
# Installation depuis un chemin local
uv add /chemin/vers/forge_auth
# Ou depuis un dépôt git
uv add git+https://exemple.com/forge_auth.git
# Ou en mode editable pendant le développement du package lui-même
uv pip install -e /chemin/vers/forge_auth
Dépendances installées automatiquement : django, djangorestframework, djangorestframework-simplejwt, pyotp, drf-spectacular.
Configuration rapide
Dans settings.py du projet hôte :
INSTALLED_APPS = [
# ...
"django.contrib.auth",
"django.contrib.contenttypes",
"rest_framework",
"rest_framework_simplejwt.token_blacklist",
"forge_auth",
]
AUTH_USER_MODEL = "forge_auth.User"
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"forge_auth.authentification.JWTAuthenticationFlexible",
],
}
# Nécessaire uniquement si vous voulez l'authentification Django classique
# (admin, formulaires) avec plusieurs champs de login.
AUTHENTICATION_BACKENDS = [
"forge_auth.backends.MultiFieldBackend",
"django.contrib.auth.backends.ModelBackend",
]
FORGE_AUTH = {} # voir section "Référence complète" et "Scénarios"
Dans urls.py du projet hôte :
from django.urls import include, path
urlpatterns = [
path("api/", include("forge_auth.urls")),
]
Les routes de forge_auth.urls incluent déjà le préfixe forge_auth/ : avec l'exemple ci-dessus, l'endpoint de connexion devient /api/forge_auth/users/login/.
Puis :
python manage.py migrate
Référence complète des options FORGE_AUTH
Toutes les clés sont optionnelles ; les valeurs ci-dessous sont les valeurs par défaut.
| Clé | Type | Défaut | Rôle |
|---|---|---|---|
USERNAME_FIELD |
"phone_number" | "email" |
"phone_number" |
Champ utilisé comme identifiant principal de connexion. |
ALTERNATIVE_USERNAME_FIELDS |
list[str] |
[] |
Champs additionnels acceptés comme identifiant (ex. ["email"]). |
OPTIONAL_FIELDS |
list["status" | "otp_secret"] |
[] |
Champs à retirer du modèle User. Présents dans cette liste = désactivés. |
OTP |
dict |
voir ci-dessous | Configuration du système OTP. |
OTP.USE_OTP |
bool |
True |
Active la connexion par code OTP plutôt que par mot de passe. |
OTP.OTP_LIFETIME |
int (secondes) |
300 |
Durée de vie indicative du code (non appliquée automatiquement, voir plus bas). |
OTP.OTP_DIGITS |
int |
4 |
Nombre de chiffres du code généré. |
OTP.OTP_CANAL |
"SMS" | "APP" | "MAIL" | "WHATSAPP" |
"WHATSAPP" |
Canal prévu pour la distribution du code (métadonnée, voir "Points non automatisés"). |
JWT |
dict |
voir ci-dessous | Configuration de la distribution des tokens. |
JWT.VIA_JSON |
bool |
True |
Renvoie access/refresh dans le corps JSON de la réponse de login. |
JWT.VIA_HTTP_ONLY |
bool |
False |
Pose access/refresh en cookies httponly. |
REGISTER_INCLUDE_IN_OTP |
bool |
False |
Si True, obtain-otp crée l'utilisateur s'il n'existe pas encore (auto-inscription via OTP). |
CREDENTIALS_SUPERUSER |
dict {username, password} |
{"username": "admin", "password": "admin"} |
Réservé, voir "Points non automatisés". |
GROUP_DEFAULT |
str | None |
None |
Réservé, voir "Points non automatisés". |
GROUPS |
list[str] |
[] |
Réservé, voir "Points non automatisés". |
Toute clé inconnue ou mal typée fait échouer le démarrage de Django avec un message listant précisément les erreurs (ImproperlyConfigured).
Scénarios de configuration détaillés
Scénario 1 — Défaut : téléphone + OTP WhatsApp
Aucune configuration nécessaire :
FORGE_AUTH = {}
Flux de connexion :
POST /forge_auth/users/pour créer le compte (phone_numberrequis).POST /forge_auth/users/obtain-otp/avec{"username": "<phone_number>"}génère et stocke un code.POST /forge_auth/users/login/avec{"username": "<phone_number>", "code": "<code>"}.
Scénario 2 — Email + mot de passe classique, sans OTP ni statut
FORGE_AUTH = {
"USERNAME_FIELD": "email",
"OPTIONAL_FIELDS": ["status", "otp_secret"],
"OTP": {"USE_OTP": False},
}
OPTIONAL_FIELDS retire StatusMixin et OtpSecretMixin du modèle User ; OtpToken redevient une classe factice. Flux de connexion :
POST /forge_auth/users/login/
{"username": "alice@exemple.com", "password": "motdepasse"}
Voir "Avertissement sur les migrations" avant d'utiliser ce scénario en production.
Scénario 3 — Identifiant multiple (email ou téléphone) + mot de passe
FORGE_AUTH = {
"USERNAME_FIELD": "email",
"ALTERNATIVE_USERNAME_FIELDS": ["phone_number"],
"OPTIONAL_FIELDS": ["otp_secret"],
"OTP": {"USE_OTP": False},
}
L'utilisateur peut se connecter en envoyant indifféremment son email ou son numéro dans le champ username. Pensez à garder MultiFieldBackend dans AUTHENTICATION_BACKENDS si vous utilisez aussi l'authentification Django standard (admin, par exemple).
Scénario 4 — JWT uniquement en cookies httponly (pas de token dans le corps JSON)
FORGE_AUTH = {
"JWT": {"VIA_JSON": False, "VIA_HTTP_ONLY": True},
}
La réponse de login ne contient alors pas de corps JSON exploitable côté client JavaScript ; les cookies access et refresh sont posés directement par le serveur. Adapté à un frontend servi par le même domaine, qui n'a pas besoin de manipuler les tokens lui-même. Le cookie est marqué secure automatiquement dès que DEBUG = False.
Scénario 5 — OTP par SMS, statut désactivé, OTP conservé
FORGE_AUTH = {
"OPTIONAL_FIELDS": ["status"],
"OTP": {"OTP_CANAL": "SMS", "OTP_DIGITS": 6},
}
Le champ status (vérification/blocage de compte) disparaît du modèle, mais l'OTP reste actif avec un code à 6 chiffres. OTP_CANAL est une métadonnée que votre code applicatif peut lire (forge_auth_config.otp_conf.OTP_CANAL) pour choisir le bon prestataire d'envoi — voir "Points non automatisés".
Scénario 6 — Auto-inscription par OTP (pas de formulaire d'inscription)
FORGE_AUTH = {
"REGISTER_INCLUDE_IN_OTP": True,
}
POST /forge_auth/users/obtain-otp/ avec un numéro inconnu crée silencieusement l'utilisateur avant de générer le code, au lieu de renvoyer une erreur de validation. Utile pour un flux "connexion = inscription" piloté uniquement par numéro de téléphone.
Endpoints de l'API
Chemins relatifs au préfixe forge_auth/ exposé par forge_auth.urls.
| Méthode | Chemin | Action | Authentification requise |
|---|---|---|---|
| GET | groups/ |
Liste des groupes | Non |
| GET | groups/{id}/ |
Détail d'un groupe | Non |
| POST | users/ |
Inscription | Non |
| GET | users/ |
Liste des utilisateurs | Oui |
| GET | users/{id}/ |
Détail d'un utilisateur | Oui |
| PATCH / PUT | users/{id}/ |
Modification d'un utilisateur | Oui |
| DELETE | users/{id}/ |
Suppression d'un utilisateur | Oui |
| POST | users/verify-email/ |
Vérifie si un email existe déjà | Non |
| POST | users/verify-phone/ |
Vérifie si un téléphone existe déjà | Non |
| GET | users/current/ |
Utilisateur courant | Oui |
| POST | users/login/ |
Connexion (mot de passe ou OTP selon config) | Non |
| POST | users/logout/ |
Déconnexion (blackliste le refresh token) | Oui |
| GET | users/session-check/ |
Vérifie que la session/JWT est valide | Oui |
| POST | users/refresh/ |
Rafraîchit le token d'accès | Oui |
| POST | users/obtain-otp/ |
Génère et stocke un code OTP | Non |
Exemples d'utilisation
Inscription (scénario par défaut, téléphone) :
curl -X POST http://localhost:8000/api/forge_auth/users/ \
-H "Content-Type: application/json" \
-d '{"phone_number": "+225000000001", "email": "alice@exemple.com"}'
Demande de code OTP :
curl -X POST http://localhost:8000/api/forge_auth/users/obtain-otp/ \
-H "Content-Type: application/json" \
-d '{"username": "+225000000001"}'
Connexion avec code OTP :
curl -X POST http://localhost:8000/api/forge_auth/users/login/ \
-H "Content-Type: application/json" \
-d '{"username": "+225000000001", "code": "1234"}'
Réponse (mode JWT.VIA_JSON = True) :
{
"access": "<jwt>",
"refresh": "<jwt>",
"user": {"pk": 1, "phone_number": "+225000000001", "email": "alice@exemple.com", "...": "..."}
}
Connexion avec mot de passe (OTP désactivé) :
curl -X POST http://localhost:8000/api/forge_auth/users/login/ \
-H "Content-Type: application/json" \
-d '{"username": "alice@exemple.com", "password": "motdepasse"}'
Appel authentifié (header) :
curl http://localhost:8000/api/forge_auth/users/current/ \
-H "Authorization: Bearer <access>"
Rafraîchissement du token :
curl -X POST http://localhost:8000/api/forge_auth/users/refresh/ \
-H "Authorization: Bearer <access>" \
-H "Content-Type: application/json" \
-d '{"refresh": "<refresh>"}'
Déconnexion :
curl -X POST http://localhost:8000/api/forge_auth/users/logout/ \
-H "Authorization: Bearer <access>" \
-H "Content-Type: application/json" \
-d '{"refresh": "<refresh>"}'
Vérification d'unicité avant inscription (front-end) :
curl -X POST http://localhost:8000/api/forge_auth/users/verify-email/ \
-H "Content-Type: application/json" \
-d '{"verify": "alice@exemple.com"}'
Modèle User : méthodes et propriétés utiles
user.username: retourne la valeur du champ configuré commeUSERNAME_FIELD.user.full_name:"Prénom Nom".user.is_valid_email/user.is_valid_phone_number: validité syntaxique.User.get(username): recherche surUSERNAME_FIELDetALTERNATIVE_USERNAME_FIELDS, lèveUser.DoesNotExistouPermissionError(compte au statutdeleted, uniquement sistatusest activé).- Si
statusest activé :user.is_verified,user.is_unauthorized, et les méthodesmark_as_verified(),mark_as_unverified(),mark_as_suspended(),deactivate_user(),delete_user(). - Si
otp_secretest activé etOTP.USE_OTPestTrue:user.otp_token.generate_otp()/user.otp_token.verify_otp(code).
Avertissement sur les migrations
Les migrations fournies (0001_initial, 0002_user_otp_secret_user_status, 0003_otptoken) ont été générées pour la configuration par défaut, c'est-à-dire OPTIONAL_FIELDS = [] (les deux champs status et otp_secret, ainsi que le modèle OtpToken, existent en base).
OPTIONAL_FIELDS ne modifie que la classe Python User au chargement de l'application ; il ne régénère pas les migrations. Si vous changez OPTIONAL_FIELDS après avoir appliqué ces migrations sur une base existante, makemigrations détectera un écart (le modèle n'a plus les champs que les migrations ont créés) et vous devrez générer puis appliquer vos propres migrations de suppression. Si vous démarrez un projet neuf avec OPTIONAL_FIELDS déjà fixé, faites-le avant la toute première migrate, ou régénérez les migrations vous-même.
Points non automatisés (à implémenter côté projet hôte)
Ces options de FORGE_AUTH sont validées au démarrage mais ne déclenchent aucune action automatique dans le code fourni :
OTP.OTP_CANAL:obtain-otpgénère et stocke le code (otp_token.otp_code), mais ne l'envoie nulle part. L'envoi effectif (SMS, WhatsApp, email) est à la charge du projet hôte, par exemple via un signalpost_savesurOtpTokenou en surchargeant l'actionobtain_otp.OTP.OTP_LIFETIME: aucune expiration n'est vérifiée dansverify_otp(). À implémenter si nécessaire (comparaison avecotp_token.updated_at).
automatisatino realiser pour ces ancien issue
CREDENTIALS_SUPERUSER: stocké dans la configuration mais aucune commande de gestion ne l'utilise pour créer un superutilisateur automatiquement.GROUP_DEFAULTetGROUPS: stockés dans la configuration mais aucun signal ne crée les groupes ni n'assigneGROUP_DEFAULTaux nouveaux utilisateurs.
Notes de sécurité
OtpToken.verify_otp()retourne toujoursTruelorsquesettings.DEBUG = True, quel que soit le code fourni. Ne déployez jamais avecDEBUG = True.- Les cookies JWT (
JWT.VIA_HTTP_ONLY) sont posés avecsecure=Truedès queDEBUG = False. En développement local sans HTTPS, gardezDEBUG = Truepour que les cookies soient acceptés par le navigateur. rest_framework_simplejwt.token_blacklistdoit être dansINSTALLED_APPSpour quelogoutpuisse réellement blacklister le refresh token (sinon l'appel échoue silencieusement, capturé par unexcept Exception: pass).
Lancer les tests
uv sync
uv run pytest
La configuration de test se trouve dans tests/settings.py et tests/urls.py. Le fichier tests/tests.py dépend du package externe forge_test (ForgeCase, ConfigForgeCase), non inclus dans cette distribution : ajoutez-le comme dépendance de développement dans pyproject.toml pour que cette suite s'exécute.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_forge_auth-0.1.2.tar.gz.
File metadata
- Download URL: django_forge_auth-0.1.2.tar.gz
- Upload date:
- Size: 85.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
695773c012781f8c89989d1b490ef23344c561cbebc8ca8b240548cc2e3857e2
|
|
| MD5 |
c32bffcd21559ff5d1d000d31b224e76
|
|
| BLAKE2b-256 |
09a85f5717bdae984f14a419076e22ec4448d7f587e082913b280db8b0a7d2b9
|
File details
Details for the file django_forge_auth-0.1.2-py3-none-any.whl.
File metadata
- Download URL: django_forge_auth-0.1.2-py3-none-any.whl
- Upload date:
- Size: 35.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d18ad92bcfe6bc441192280fa36b56b2f485126c8078950ecfab6ec4ac1e6f84
|
|
| MD5 |
958f1bf7d5f752bdbbe6f464ef6697ae
|
|
| BLAKE2b-256 |
a8d0c8ea3d0342f6a9bbbaa02f152fcb1cebd379317d4e6c728b6dbba90659b5
|