Rustango docs
← Authentification

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. La chaîne n'est qu'une liste de configuration, câblée à axum. Associez-la à require_auth / require_perm pour verrouiller les routes et à l'extracteur CurrentUser pour lire le résultat.

Les backends d'authentification dans Rustango : une requête traverse une chaîne de backends (ModelBackend, ApiKeyBackend, JwtBackend) ; le premier à reconnaître l'identifiant l'emporte et injecte CurrentUser, puis require_perm vérifie un codename

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) et rustango::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

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 (avec require_auth) ou poursuit de manière anonyme (avec optional_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

BackendIdentifiant qu'il litIdentifie un utilisateur par
ModelBackendAuthorization: Basic <base64(user:pass)>nom d'utilisateur + vérification du mot de passe argon2id contre rustango_users
ApiKeyBackendAuthorization: Bearer <prefix.secret>la table rustango_api_keys (voir Clés d'API)
JwtBackendAuthorization: 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())),
];

JwtBackend accepte les tokens d'accès émis par JwtLifecycle et refuse ses tokens de rafraîchissement — les deux sont identiques sur le fil à typ près, donc un token de rafraîchissement présenté comme bearer serait une authentification valant des jours au lieu de quelques minutes.

La révocation est optionnelle et désactivée par défaut. Un JwtBackend nu ne consulte jamais de liste noire : un token révoqué par /api/auth/logout continue donc de s'authentifier via ce backend jusqu'à sa propre expiration. Partagez un magasin entre le cycle de vie et le backend pour que la déconnexion prenne effet :

use rustango::jti_store::{InMemoryJtiStore, JtiStore};

let shared: Arc<dyn JtiStore> = Arc::new(InMemoryJtiStore::new()); // Redis en production
let lifecycle = JwtLifecycle::new(secret.clone()).with_jti_store(Arc::clone(&shared));
let backend = JwtBackend::new(secret).with_jti_store(Arc::clone(&shared));

Ce doit être le même magasin. En câbler deux a l'air configuré et n'applique rien : la déconnexion écrit dans l'un et la vérification lit l'autre. Voir révocation et le magasin de JTI.

É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);           // 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 CurrentUser est infaillible, oublier require_auth ne provoque pas d'échec de compilation — chaque requête voit simplement None. Derrière require_auth, les requêtes anonymes reçoivent déjà un 401, donc user y est toujours Some.


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");     // inner: needs the codename

let app = Router::new()
    .route("/profile", get(profile))
    .merge(admin)
    .require_auth(backends);       // 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 des backends d'authentification interchangeables 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 / JwtBackend consomment.
  • Mots de passe — le hachage contre lequel ModelBackend vé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.