Backends d'authentification
Un backend d'authentification répond à une seule question : étant donné une
requête entrante, qui est l'utilisateur ? Rustango vous permet d'en empiler
plusieurs — HTTP Basic, clé d'API, JWT — dans une chaîne que le middleware
d'authentification essaie dans l'ordre, de sorte qu'une même application peut
accepter humains et machines sur les mêmes routes. C'est l'idée
AUTHENTICATION_BACKENDS de Django, câblée à axum. Associez-la à
require_auth / require_perm pour verrouiller les routes et à l'extracteur
CurrentUser pour lire le résultat.
Un terme vous est inconnu ici ? Backend, middleware, extracteur, codename de permission — voir le glossaire.
Source :
rustango::tenancy::auth_backends(AuthBackend,ModelBackend,ApiKeyBackend,JwtBackend,AuthUser,AuthError) etrustango::tenancy::{RouterAuthExt, CurrentUser}— derrière la fonctionnalitétenancy. Un registre portable et indépendant de la base de données vit aussi àrustango::auth_backends(toujours compilé).Version exécutable : chaque extrait est copié depuis
auth_backends_doc.rs(cargo test -p rustango --features sqlite,tenancy --test auth_backends_doc).
Table des matières
- La chaîne · Les backends intégrés
- Verrouiller les routes : require_auth
- Lire l'utilisateur : CurrentUser
- Permissions : require_perm
- Le registre portable
- Voir aussi
La chaîne
Vous passez à require_auth un Vec<Arc<dyn AuthBackend>>. À chaque requête, le
middleware les essaie dans l'ordre :
- le premier backend qui reconnaît l'identifiant l'emporte (renvoie l'utilisateur) ;
- un backend qui ne le reconnaît pas renvoie « aucun » et le suivant est essayé ;
- si un backend échoue durement (par ex. un compte inactif sur un jeton valide), la chaîne s'arrête avec cette erreur ;
- si aucun ne correspond, la requête reçoit un
401(avecrequire_auth) ou poursuit de manière anonyme (avecoptional_auth).
use std::sync::Arc;
use rustango::tenancy::auth_backends::{ApiKeyBackend, AuthBackend, ModelBackend};
let backends: Vec<Arc<dyn AuthBackend>> = vec![
Arc::new(ModelBackend), // HTTP Basic → humans
Arc::new(ApiKeyBackend), // Bearer key → machines
];
Les backends intégrés
| Backend | Identifiant qu'il lit | Identifie un utilisateur par |
|---|---|---|
ModelBackend | Authorization: Basic <base64(user:pass)> | nom d'utilisateur + vérification du mot de passe argon2id contre rustango_users |
ApiKeyBackend | Authorization: Bearer <prefix.secret> | la table rustango_api_keys (voir Clés d'API) |
JwtBackend | Authorization: Bearer <jwt> | un jeton HS256 signé (voir JWT) |
ApiKeyBackend et JwtBackend lisent tous deux Bearer et lèvent l'ambiguïté
par la forme (le premier segment séparé par un point d'une clé d'API fait
exactement 8 caractères). Construisez JwtBackend avec un secret d'au moins 32
octets (JwtBackend::new(secret) panique sinon) :
use rustango::tenancy::auth_backends::JwtBackend;
let backends: Vec<Arc<dyn AuthBackend>> = vec![
Arc::new(ModelBackend),
Arc::new(JwtBackend::new(jwt_secret_at_least_32_bytes.to_vec())),
];
Écrivez un backend personnalisé en implémentant le trait (une seule méthode async
qui inspecte les Parts de la requête et renvoie Option<AuthUser>) :
use async_trait::async_trait; // add `async-trait` to your Cargo.toml
use axum::http::request::Parts;
use rustango::sql::Pool;
use rustango::tenancy::auth_backends::{AuthBackend, AuthError, AuthUser};
struct HeaderBackend;
#[async_trait]
impl AuthBackend for HeaderBackend {
async fn authenticate(&self, parts: &Parts, _pool: &Pool)
-> Result<Option<AuthUser>, AuthError>
{
// ...inspect parts.headers, return Some(AuthUser{..}) or Ok(None)
Ok(None)
}
}
Verrouiller les routes : require_auth
RouterAuthExt ajoute le middleware. require_auth rejette les requêtes
anonymes avec un 401 ; optional_auth les laisse passer (de sorte qu'un
handler peut se ramifier selon connecté ou non) :
use rustango::tenancy::RouterAuthExt;
let app = Router::new()
.route("/profile", get(profile))
.require_auth(backends, pool); // 401 if no backend matches
Comportement vérifié :
// no credentials → 401
// Basic alice:<correct> → 200
// Basic alice:<wrong> → 401 (no backend accepted; no enumeration)
// Bearer <valid api key> → 200
Lire l'utilisateur : CurrentUser
Les handlers lisent l'utilisateur authentifié avec l'extracteur CurrentUser.
Il est infaillible — Some(user) lorsqu'un backend en a résolu un, None sinon :
use rustango::tenancy::CurrentUser;
async fn profile(CurrentUser(user): CurrentUser) -> Response {
match user {
Some(u) => format!("hello {}", u.username).into_response(),
None => StatusCode::UNAUTHORIZED.into_response(),
}
}
Piège : parce que
CurrentUserest infaillible, oublierrequire_authne provoque pas d'échec de compilation — chaque requête voit simplementNone. Derrièrerequire_auth, les requêtes anonymes reçoivent déjà un401, doncusery est toujoursSome.
Permissions : require_perm
require_perm verrouille une route sur un codename de permission
({table}.{action}, par ex. post.add). Appliquez-le au sous-routeur interne et
require_auth à l'externe, afin que l'utilisateur soit résolu avant que la
permission ne soit vérifiée :
let admin = Router::new()
.route("/admin", get(admin_only))
.require_perm("post.add", pool.clone()); // inner: needs the codename
let app = Router::new()
.route("/profile", get(profile))
.merge(admin)
.require_auth(backends, pool); // outer: resolves the user first
// alice (granted post.add) → /admin 200
// bob (authed, no grant) → /admin 403
// anonymous → /admin 401 (auth runs first)
Résolution : un superutilisateur (actif) passe tout ; un utilisateur
désactivé est refusé même avec des octrois ; une surcharge explicite par
utilisateur l'emporte sur les octrois de rôle ; sinon, tout rôle détenu par
l'utilisateur qui octroie le codename passe. Octroyez avec
set_user_perm_pool / les rôles via create_role_pool + assign_role_pool (les
tables de permissions sont créées par ensure_tables_pool).
Le registre portable
Séparément, rustango::auth_backends (à noter : racine de crate, et non
tenancy) est un petit registre indépendant du framework — une chaîne
Credentials → Principal dotée de son propre trait AuthBackend. Il n'a aucune
glu HTTP/axum ; utilisez-le lorsque vous voulez une pluggabilité de backend à la
Django au sein de votre propre code d'authentification :
use rustango::auth_backends::{AuthBackendChain, Credentials, RemoteUserBackend};
let chain = AuthBackendChain::new().with(Arc::new(RemoteUserBackend::trust_username()));
let principal = chain.authenticate(&Credentials::remote("alice")).await?;
Mêmes sémantiques « le premier succès l'emporte / la première erreur arrête » que
la chaîne HTTP. Pour verrouiller de vraies routes, utilisez le middleware
tenancy ci-dessus.
Voir aussi
- Clés d'API et JWT — les identifiants que
ApiKeyBackend/JwtBackendconsomment. - Mots de passe — le hachage contre lequel
ModelBackendvérifie. - Décorateurs d'accès — verrouillage par handler
login_required/permission_required, l'alternative de style décorateur àrequire_auth/require_perm. - Sessions — authentification par cookie pour les navigateurs.
