Aller au contenu

Liaison avec le compte AE

Le site AE offre des mécanismes permettant aux applications tierces de récupérer les informations sur un utilisateur du site AE. De cette manière, il devient possible de synchroniser les informations qu possède l'application tierce sur l'utilisateur, directement depuis le site AE.

Fonctionnement général

Pour authentifier vos utilisateurs, vous aurez besoin d'un serveur web et d'un client d'API (celui auquel est liée votre clef d'API). Deux informations vous sont nécessaires, en plus de votre clef d'API :

  • l'id du client : vous pouvez l'obtenir soit en le demandant à l'équipe info, soit en appelant la route GET /api/client/me avec votre clef d'API renseignée dans le header X-APIKey
  • la clef HMAC du client : vous devez la demander à l'équipe info.

Ces deux éléments ont une fonction différente : l'id du client permet de dire au serveur quelle est l'application qui s'adresse à lui, tandis que la clef HMAC servira à créer une signature unique permettant de s'assurer que les données transmises n'ont pas été falsifiées.

Grâce à ces informations, vous allez pouvoir fournir le contexte nécessaire au site AE pour qu'il authentifie vos utilisateurs.

En effet, la démarche d'authentification s'effectue presque entièrement sur le site : le travail de l'application tierce consiste uniquement à fournir à l'utilisateur une url avec les bons paramètres, puis à recevoir la réponse du serveur si tout s'est bien passé.

Comme un dessin vaut parfois mieux que mille mots, voici les diagrammes décrivant le processus. L'un montre l'entièreté de la démarche ; l'autre dans un souci de simplicité, ne montre que ce qui est visible directement par l'application tierce.

sequenceDiagram
    actor User
    participant App
    User->>+App: Authentifie-moi, stp
    App-->>-User: url de connexion<br/>avec signature
    User->>+Sith: GET url
    opt Utilisateur non-connecté
        Sith->>+User: Formulaire de connexion
        User-->>-Sith: Connexion
    end
    Sith->>Sith: vérification de la signature
    Sith->>+User: Formulaire<br/>des conditions<br/>d'utilisation
    User-->>-Sith: Validation
    Sith->>+App: URL de retour<br/>avec données utilisateur
    App->>App: Traitement des <br/>données utilisateur
    App-->>-Sith: 204 OK, No content
    Sith-->>-User: Message de succès
    App--)User: Message de succès
sequenceDiagram
    actor User
    participant App
    User->>+App: Authentifie-moi, stp
    App-->>-User: url de connexion<br/>avec signature
    opt
        Sith->>+App: URL de retour<br/>avec données utilisateur
        App->>App: Traitement des <br/>données utilisateur
        App-->>-Sith: 204 OK, No content
        App--)User: Message de succès
    end

Données attendues

URL de connexion

L'URL de connexion que vous allez fournir à l'utilisateur doit être https://ae.utbm.fr/api-link/auth/ et doit contenir les données décrites dans ThirdPartyAuthParamsSchema :

  • client_id (integer) : l'id de votre client, que vous pouvez obtenir de la manière décrite plus haut
  • third_party_app(string) : le nom de la plateforme pour laquelle l'authentification va être réalisée (si votre application est un bot discord, mettez la valeur "discord")
  • privacy_link(URL) : l'URL vers la page de politique de confidentialité qui s'appliquera dans le cadre de l'application (s'il s'agit d'un bot discord, donnez le lien vers celles de Discord)
  • username(string) : le pseudonyme que l'utilisateur possède sur votre application
  • callback_url(URL) : l'URL que le site AE appellera si l'authentification réussit
  • signature(string) : la signature des données de la requête. Il s'agit d'une signature par clef HMAC dont le fonctionnement est détaillé plus bas.

Ces données doivent être url-encodées et passées dans les paramètres GET.

URL de retour

Les URLs fournies doivent être des URLs HTTP valides. En outre, elles doivent obligatoirement inclure la barre oblique finale.

https://exemple.ae.utbm.fr/foo/

https://exemple.ae.utbm.fr/foo

Tip

Inclure l'id de votre utilisateur dans l'URL de retour peut être un bon moyen de l'identifier lors du callback. Par exemple : GET /callback/{int:user_id}/.

Example

Supposons que votre client d'API soit utilisé dans le cadre d'un bot Discord, avec les données suivantes :

  • l'id du client est 15
  • sa clef HMAC est "beb99dd53" (c'est pour l'exemple, une vraie clef sera beaucoup plus longue)
  • le pseudonyme discord de votre utilisateur est Brian
  • son id sur discord est 123456789
  • votre route de callback est GET /callback/{int:user_id}/, accessible au domaine https://bot.ae.utbm.fr

Alors les paramètres de votre URL seront :

Paramètre valeur
client_id 15
third_party_app discord
privacy_link https://discord.com/privacy
username Brian
callback_url https://bot.ae.utbm.fr/callback/123456789/
signature 1a383c51060be64f07772aa42e07
18ae096b8f21f2cdb4061c0834a416d12101

Et l'url fournie à l'utilisateur sera :

https://ae.utbm.fr/api-link/auth/?client_id=15&third_party_app=discord &privacy_link=https%3A%2F%2Fdiscord.com%2Fprivacy&username=Brian &callback_url=https%3A%2F%2Fbot.ae.utbm.fr%2Fcallback%2F123456789%2F &signature=1a383c51060be64f07772aa42e0718ae096b8f21f2cdb4061c0834a416d12101

Données de retour

Si l'authentification réussit, le site AE enverra une requête HTTP POST à l'URL de retour fournie dans l'URL de connexion.

Le corps de la requête de callback et au format JSON et contient deux paires clef-valeur :

  • user : les données utilisateur, telles que décrites par UserProfileSchema
  • signature : la signature des données utilisateur
Example

En reprenant les mêmes paramètres que dans l'exemple précédent, le site AE pourra renvoyer à l'application la requête suivante :

POST https://bot.ae.utbm.fr/callback/123456789/
content-type: application/json
body: {
    "user": {
        "id": 144131,
        "nick_name": "inzekitchen",
        "first_name": "Brian",
        ...
    },
    "signature": "f16955bab6b805f6e1abbb98a86dfee53fed0bf812aa6513ca46cfd461b70020"
}

L'application doit répondre avec un des codes HTTP suivants :

Code Raison
204 Tout s'est bien passé
403 Les données de retour ne sont
pas signées ou sont mal signées
404 L'URL de retour ne permet pas
d'identifier un utilisateur de l'application

Code d'erreur par défaut

Si l'appel de la route fait face à plusieurs problèmes en même temps (par exemple, l'URL ne permet pas de retrouver votre utilisateur, et en plus les données sont mal signées), le 403 prime et doit être retourné par défaut.

Signature des données

Les données de l'URL de connexion doivent être signées, et la signature de l'URL de retour doit être vérifiée.

Dans le deux cas, la signature est le digest HMAC-SHA512 des données url-encodées, en utilisant la clef HMAC du client d'API. L'ordre dans lequel ces données sont placées dans l'encodage URL doit être strictement le même que celui donné plus haut.

Signature de l'URL de connexion

En reprenant le même exemple que les fois précédentes, l'url-encodage des données est :

client_id=15&third_party_app=discord &privacy_link=https%3A%2F%2Fdiscord.com%2Fprivacy%2F&username=Brian &callback_url=https%3A%2F%2Fbot.ae.utbm.fr%2Fcallback%2F123456789%2F

Notez que la signature n'est pas (encore) dedans. Cette dernière peut-être obtenue avec le code suivant :

Dépendances :

  • environs (>=14.1)
import hmac
from urllib.parse import urlencode

from environs import Env

env = Env()
env.read_env()

key = env.str("HMAC_KEY").encode()
data = {
    "client_id": 15,
    "third_party_app": "discord",
    "privacy_link": "https://discord.com/privacy/",
    "username": "Brian",
    "callback_url": "https://bot.ae.utbm.fr/callback/123456789/",
}
urlencoded = urlencode(data)
data["signature"] = hmac.digest(key, urlencoded.encode(), "sha512").hex()

# URL a fournir à l'utilisateur pour son authentification
user_url = f"https://ae.ubtm.fr/api-link/auth/?{urlencode(data)}"

Dépendances :

  • hmac (>=0.12.1)
  • url (>=2.5.7, features serde)
  • serde (>=1.0.228, features derive)
  • serde_urlencoded (>=0.7.1)
  • sha2 (>=0.10.9)
  • dotenvy (>= 0.15)
use hmac::{Mac, SimpleHmac};
use serde::Serialize;
use sha2::Sha512;
use url::Url;

#[derive(Serialize, Debug)]
struct UrlData<'a> {
    client_id: u32,
    third_party_app: &'a str,
    privacy_link: Url,
    username: &'a str,
    callback_url: Url,
}

impl<'a> UrlData<'a> {
    pub fn signature(&self, key: &[u8]) -> CtOutput<SimpleHmac<Sha512>> {
        let urlencoded = serde_urlencoded::to_string(self).unwrap();
        SimpleHmac::<Sha512>::new_from_slice(key)
            .unwrap()
            .chain_update(urlencoded.as_bytes())
            .finalize()
    }
}

impl Into<Url> for UrlData<'_> {
    fn into(self) -> Url {
        let key = std::env::var("HMAC_KEY").unwrap();
        let mut url = Url::parse("http://ae.utbm.fr/api-link/auth/").unwrap();
        url.set_query(Some(
            format!(
                "{}&signature={:x}",
                serde_urlencoded::to_string(&self).unwrap(),
                self.signature(key.as_bytes()).into_bytes()
            )
            .as_str(),
        ));
        url
    }
}

fn main() {
    dotenvy::dotenv().expect("Couldn't load env");
    let data = UrlData {
        client_id: 1,
        third_party_app: "discord",
        privacy_link: "https://discord.com/privacy/".parse().unwrap(),
        username: "Brian",
        callback_url: "https://bot.ae.utbm.fr/callback/123456789/"
            .parse()
            .unwrap(),
    };
    let url: Url = data.into();
    println!("{:?}", url);
}
Vérification de la signature de la réponse

Les données utilisateur peuvent ressembler à :

{
    "user": {
        "display_name": "Matthieu Vincent",
        "profile_url": "/user/380/",
        "profile_pict": "/static/core/img/unknown.jpg",
        "id": 380,
        "nick_name": None,
        "first_name": "Matthieu",
        "last_name": "Vincent",
    },
    "signature": "3802a280fbb01bd9fetc."
}

Vous pouvez vérifier la signature ainsi :

    import hmac
    from urllib.parse import urlencode

    from environs import Env

    env = Env()
    env.read_env()

    def is_signature_valid(user_data: dict, signature: str) -> bool:
        key = env.str("HMAC_KEY").encode()
        urlencoded = urlencode(user_data)
        return hmac.compare_digest(
            hmac.digest(key, urlencoded.encode(), "sha512").hex(),
            signature,
        )


    post_data = <récupération des données POST>
    print(
        "signature valide :", 
        is_signature_valid(post_data["user"], post_data["signature"])
    )

Warning

Vous devez impérativement vérifier la signature des données de la requête de callback !

Ne pas vérifier la signature permet à n'importe quel acteur tierce malveillant de vous appeler sur votre callback. Ce serait une faille de sécurité majeure de votre côté.

Si l'équipe informatique se rend compte que vous ne le faites pas, elle se réserve le droit de suspendre votre application, immédiatement et sans préavis.