Back to flin
flin

Authentification WhatsApp OTP pour l'Afrique

Comment FLIN fournit une authentification WhatsApp OTP intégrée -- la méthode d'authentification phone-first conçue pour les marchés africains où WhatsApp est la plateforme de communication principale.

Thales & Claude | March 30, 2026 9 min flin
EN/ FR/ ES
flinwhatsappotpafricaauthentication

Dans la Silicon Valley, la méthode d'authentification par défaut est l'e-mail et le mot de passe, optionnellement améliorée par Google Sign-In. Cette hypothèse échoue spectaculairement en Afrique, où plus de 300 millions de personnes utilisent WhatsApp quotidiennement mais beaucoup n'ont pas d'adresse e-mail personnelle. Un étudiant à Abidjan, un commerçant à Lagos, un enseignant à Nairobi -- ils communiquent par WhatsApp, paient par mobile money et s'identifient par numéro de téléphone.

Construire FLIN sans authentification WhatsApp serait comme construire un framework web américain sans Google Sign-In. Cela fonctionnerait techniquement, mais ignorerait comment le public cible vit réellement.

FLIN fournit WhatsApp OTP comme méthode d'authentification intégrée. Trois fonctions -- whatsapp_send_otp(), otp_generate(), et le schéma de vérification standard -- gèrent l'ensemble du flux. Pas de SDK Twilio. Pas de service d'authentification tiers. Pas de surprises de facturation par message.

Le flux d'authentification WhatsApp

WhatsApp OTP utilise un flux en 3 étapes pour les nouveaux utilisateurs et un flux en 2 étapes pour les utilisateurs de retour :

Nouvel utilisateur :
1. Entrer le numéro de téléphone -> Envoyer OTP via WhatsApp
2. Entrer le code OTP -> Vérifier le code
3. Compléter le profil (nom, e-mail, avatar)
4. Créer le compte -> Tableau de bord

Utilisateur de retour :
1. Entrer le numéro de téléphone -> Envoyer OTP via WhatsApp
2. Entrer le code OTP -> Vérifier le code -> Tableau de bord (étape 3 sautée)

L'insight clé est que le numéro de téléphone est l'identité. Si le téléphone est déjà enregistré, l'utilisateur est connecté après la vérification OTP. Sinon, il complète un formulaire de profil et un compte est créé.

Étape 1 : Envoyer l'OTP

flin// app/auth/send-whatsapp-otp.flin

layout = "auth"

waPhone = session.waPhone || ""
otpSent = false

fn processSendWhatsappOtp() {
    if waPhone != "" {
        code = otp_generate(6)
        result = whatsapp_send_otp(waPhone, code)

        if result.success {
            session.waOtpCode = code
            session.waOtpPhone = waPhone
            otpSent = true
        }
    }
}
processSendWhatsappOtp()

{if otpSent}
    <h2>Entrez le code envoyé sur votre WhatsApp</h2>
    <p>Nous avons envoyé un code à 6 chiffres au {waPhone}</p>

    <input class="otp-input" type="text" bind={codeInput}
        maxlength="6" autocomplete="one-time-code" inputmode="numeric">

    <button click={
        session.waOtpInput = codeInput;
        location.href = "/auth/verify-whatsapp-otp"
    }>
        Vérifier le code
    </button>
{else}
    <p>Une erreur s'est produite. Veuillez réessayer.</p>
    <a href="/login">Retour à la connexion</a>
{/if}

La fonction otp_generate(6) crée un code aléatoire à 6 chiffres cryptographiquement sécurisé. La fonction whatsapp_send_otp() l'envoie via l'API WhatsApp Business.

Étape 2 : Vérifier l'OTP

flin// app/auth/verify-whatsapp-otp.flin

layout = "auth"

otpInput = session.waOtpInput || ""
otpCode = session.waOtpCode || ""
otpPhone = session.waOtpPhone || ""

// Effacer les données sensibles immédiatement
session.waOtpInput = none
session.waOtpCode = none

verifyOk = false
isNewUser = false

fn processVerifyWhatsappOtp() {
    if otpInput != "" && otpCode != "" && otpPhone != "" {
        if otpInput == otpCode {
            existing = User.where(phone == otpPhone && role == "User").first

            if existing != none {
                // Utilisateur de retour -- connecter directement
                session.user = existing.email
                session.userName = existing.name || existing.firstName || existing.email
                session.userId = to_text(existing.id)
                session.waOtpPhone = none
                verifyOk = true
            } else {
                // Nouvel utilisateur -- complétion de profil nécessaire
                session.waVerifiedPhone = otpPhone
                session.waOtpPhone = none
                isNewUser = true
                verifyOk = true
            }
        }
    }
}
processVerifyWhatsappOtp()

{if verifyOk && !isNewUser}
    <h2>Bon retour !</h2>
    <script>setTimeout(function() { window.location.href = "/tasks"; }, 1000);</script>
{else if verifyOk && isNewUser}
    <h2>Téléphone vérifié ! Configurons votre profil.</h2>
    <script>setTimeout(function() { window.location.href = "/auth/whatsapp-complete-profile"; }, 1200);</script>
{else}
    <h2>Code invalide</h2>
    <p>Le code que vous avez saisi est incorrect ou a expiré.</p>
    <a href="/login">Réessayer</a>
{/if}

Le schéma de sécurité critique : les données OTP sont effacées de la session immédiatement après lecture. Le code existe dans le stockage de session pour le minimum de temps possible.

Étape 3 : Complétion du profil (nouveaux utilisateurs uniquement)

flin// app/auth/whatsapp-complete-profile.flin

layout = "auth"

waPhone = session.waVerifiedPhone || ""
errorKey = session.waCreateError || ""
session.waCreateError = none

{if waPhone == ""}
    <script>window.location.href = "/login";</script>
{else}
    <h2>Complétez votre profil</h2>

    <form method="POST" action="/auth/whatsapp-create-account" enctype="multipart/form-data">
        <input type="text" name="firstName" placeholder="Prénom" required>
        <input type="text" name="lastName" placeholder="Nom de famille">
        <input type="email" name="email" placeholder="Adresse e-mail" required>
        <input type="file" name="avatar" accept="image/*">
        <input type="text" name="occupation" placeholder="Profession">
        <input type="text" name="country" placeholder="Pays">

        {if errorKey != ""}
            <p class="error">{t(errorKey)}</p>
        {/if}

        <button type="submit">Créer le compte</button>
    </form>
{/if}
flin// app/auth/whatsapp-create-account.flin

route POST {
    validate {
        firstName: text @required @minLength(1)
        email: text @required @email
        lastName: text
        occupation: text
        country: text
        avatar: file @max_size("5MB")
    }

    waPhone = session.waVerifiedPhone || ""
    if waPhone == "" { redirect("/login") }

    // Vérifier l'unicité de l'e-mail
    existingEmail = User.where(email == body.email && role == "User").first
    if existingEmail != none {
        session.waCreateError = "error.email_taken"
        redirect("/auth/whatsapp-complete-profile")
    }

    // Vérifier l'unicité du téléphone
    existingPhone = User.where(phone == waPhone && role == "User").first
    if existingPhone != none {
        session.user = existingPhone.email
        session.userName = existingPhone.name
        session.userId = to_text(existingPhone.id)
        session.waVerifiedPhone = none
        redirect("/tasks")
    }

    avatarPath = ""
    if body.avatar != none {
        avatarPath = save_file(body.avatar, ".flindb/avatars/")
    }

    fullName = to_text(body.firstName) + " " + to_text(body.lastName || "")
    newUser = User {
        email: body.email,
        name: fullName,
        firstName: body.firstName,
        lastName: body.lastName || "",
        phone: waPhone,
        provider: "WhatsApp",
        avatar: avatarPath,
        occupation: body.occupation || "",
        country: body.country || "",
        emailVerified: false
    }
    save newUser

    session.waVerifiedPhone = none
    session.user = newUser.email
    session.userName = fullName
    session.userId = to_text(newUser.id)

    redirect("/tasks")
}

La fonction whatsapp_send_otp()

La fonction intégrée gère l'intégration de l'API WhatsApp Business :

flinresult = whatsapp_send_otp(phone_number, code)
// result.success -> true/false
// result.error -> message d'erreur en cas d'échec

En coulisses, FLIN envoie l'OTP via l'API WhatsApp Business en utilisant un modèle de message pré-approuvé. Le modèle est configuré une fois dans le WhatsApp Business Manager et référencé par le runtime :

rustpub async fn send_whatsapp_otp(
    phone: &str,
    code: &str,
) -> Result<SendResult, WhatsAppError> {
    let api_url = env::var("WHATSAPP_API_URL")?;
    let token = env::var("WHATSAPP_TOKEN")?;
    let template_name = env::var("WHATSAPP_OTP_TEMPLATE")
        .unwrap_or_else(|_| "otp_verification".to_string());

    let payload = json!({
        "messaging_product": "whatsapp",
        "to": normalize_phone(phone),
        "type": "template",
        "template": {
            "name": template_name,
            "language": { "code": "en" },
            "components": [{
                "type": "body",
                "parameters": [{
                    "type": "text",
                    "text": code
                }]
            }]
        }
    });

    let response = reqwest::Client::new()
        .post(&api_url)
        .bearer_auth(&token)
        .json(&payload)
        .send()
        .await?;

    if response.status().is_success() {
        Ok(SendResult { success: true, error: None })
    } else {
        let error = response.text().await.unwrap_or_default();
        Ok(SendResult { success: false, error: Some(error) })
    }
}

Normalisation des numéros de téléphone

Les numéros de téléphone africains se présentent sous de nombreux formats : +225 07 08 09 10, 00225 0708091010, 07 08 09 10, 225708091010. FLIN normalise tous ces formats au format E.164 (+2250708091010) avant l'envoi :

rustfn normalize_phone(phone: &str) -> String {
    // Supprimer les espaces, tirets, parenthèses
    let digits: String = phone.chars()
        .filter(|c| c.is_ascii_digit() || *c == '+')
        .collect();

    if digits.starts_with('+') {
        digits
    } else if digits.starts_with("00") {
        format!("+{}", &digits[2..])
    } else if digits.len() == 10 {
        // Supposer un format local -- indicatif pays nécessaire depuis la config
        let country_code = env::var("DEFAULT_PHONE_COUNTRY").unwrap_or("225".into());
        format!("+{}{}", country_code, digits)
    } else {
        format!("+{}", digits)
    }
}

Pourquoi WhatsApp OTP pour l'Afrique

La décision d'intégrer WhatsApp OTP dans FLIN a été motivée par la réalité du marché :

Pénétration de WhatsApp. En Côte d'Ivoire, au Nigeria, au Kenya, au Ghana, en Afrique du Sud et dans la majeure partie de l'Afrique subsaharienne, WhatsApp est la plateforme de messagerie par défaut. La plupart des gens consultent WhatsApp avant de consulter leur e-mail.

Rareté de l'e-mail. De nombreux internautes africains, surtout en dehors des grandes villes, n'ont pas d'adresse e-mail personnelle. Exiger une inscription par e-mail les exclut.

Identité basée sur le téléphone. Le mobile money (MTN Mobile Money, Orange Money, Wave, M-Pesa) utilise les numéros de téléphone comme identifiants. Les services gouvernementaux acceptent de plus en plus la vérification par téléphone. Un numéro de téléphone est la forme la plus universelle d'identité numérique en Afrique.

Coûts des SMS. Les OTP par SMS sont chers en Afrique (0,03-0,10 USD par message) et peu fiables selon les opérateurs. Les messages WhatsApp coûtent une fraction de ce prix via l'API Business et sont livrés de manière fiable sur les connexions data.

Confiance. Les utilisateurs font confiance aux messages reçus sur WhatsApp. Un code de vérification reçu sur WhatsApp semble légitime. Un code reçu par SMS pourrait ressembler à du spam.

En faisant de WhatsApp OTP une fonctionnalité intégrée, FLIN se positionne comme un framework qui comprend son marché principal. Un développeur à Abidjan construisant une application pour les utilisateurs ouest-africains peut ajouter l'authentification par téléphone en quelques minutes, pas en quelques jours.

Considérations de sécurité

WhatsApp OTP a des considérations de sécurité spécifiques :

Expiration du code. Les codes OTP de FLIN sont valides pendant 10 minutes. Après cela, les données de session sont invalidées et un nouveau code doit être demandé.

Limitation du débit. Le point de terminaison d'envoi OTP doit être limité en débit pour empêcher les abus. Le garde guard rate_limit(3, 300) (3 requêtes par 5 minutes) est recommandé pour les points de terminaison OTP.

Longueur du code. Le code à 6 chiffres fournit 1 million de combinaisons possibles. Combiné avec la limitation du débit (5 tentatives par session), la force brute est impraticable.

Hygiène de session. Les données OTP sont effacées de la session immédiatement après utilisation. Le code, le numéro de téléphone et l'entrée de vérification ne sont jamais stockés plus longtemps que nécessaire.

Le WhatsApp OTP de FLIN n'est pas un wrapper autour d'un service d'authentification tiers. C'est une méthode d'authentification de première classe intégrée au langage, avec les mêmes garanties de gestion de session, de validation et de sécurité que l'authentification par e-mail/mot de passe.

Dans le prochain article, nous couvrons les validateurs de corps de requête -- comment les blocs validate de FLIN appliquent la sécurité de type, les contraintes et les règles métier sur les données entrantes avant que votre code de gestionnaire ne s'exécute.


Ceci est la partie 112 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et construit un langage de programmation à partir de zéro.

Navigation de la série : - [111] OAuth2 et authentification sociale - [112] Authentification WhatsApp OTP pour l'Afrique (vous êtes ici) - [113] Validateurs de corps de requête - [114] 75 tests de sécurité : comment nous avons tout vérifié

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles