Découverte des APIs Twitch #2 : Authentification

On reprend le développement de notre application visant à afficher des alertes sur nos lives Twitch lorsque des personnes suivent la chaîne, s'abonnent, offrent des bits, etc. Cette application sera, pour rappel, développée sans aucune bibliothèque ni aucun framework, en utilisant uniquement des technologies Web standard : HTML, CSS et JavaScript.

La dernière fois je vous avais expliqué comment déclarer l'application auprès de Twitch et comment l'intégrer à OBS ; aujourd'hui on va voir comment implémenter l'authentification, puisque comme vous le savez certainement, il faut s'authentifier pour accéder aux APIs de Twitch.

Note

Cet article fait partie d'une série consacrée à la découverte des APIs Twitch :

  1. Création d'applications et intégration à OBS
  2. Authentification

Choisir la méthode d'authentification

Twitch propose différentes méthodes d'authentifications, qui répondent à différents besoins et qui fourniront différents types de jetons (tokens).

Twitch permet d'utiliser les 2 méthodes d'authentifications suivant :

  • OAuth 2.0 : très répandu et facile à utiliser.
  • OpenID Connect : une surcouche à OAuth 2, plus sécurisée, mais plus difficile à mettre en œuvre.

Pour nos besoins, OAuth fera parfaitement l'affaire ; c'est donc lui que l'on va utiliser.

Il nous reste maintenant à déterminer par quel workflow d'OAuth on va passer... Et pour savoir cela, commençons par regarder quels types de tokens on peut obtenir et ce qu'ils permettent d'accomplir. Twitch nous permet d'obtenir 2 types de jetons d'authentifications via OAuth :

  • Un user access token, qui permet d'authentifier un utilisateur de Twitch et d'agir en son nom.
  • Un app access token, qui permet d'accéder aux ressources de l'application elle-même.

Dans notre cas, les APIs auxquelles on va vouloir accéder nécessitent un utilisateur authentifié, on va donc avoir besoin d'un user access token.

Maintenant qu'on sait ce que l'on veut, il nous reste à choisir entre l'un des deux workflow suivants qui permettent tous deux d'obtenir le jeton désiré :

  • OAuth Implicite Code Flow : en résumé on utilise ce workflow lorsque l'on ne peut pas garder un « secret », ce qui est le cas lorsque l'on développe une application en JavaScript côté client (c'est notre cas) ou une application mobile, puisque l'utilisateur final pourra toujours aller fouiner dans le code de l'application pour trouver ce secret.
  • OAuth Authorization Code Flow : méthode d'authentification à privilégier lorsque l'on dispose d'un serveur, puisque ce dernier pourra faire l'intermédiaire entre l'application côté client et Twitch ce qui lui permettra de conserver un jeton secret qui ne sera jamais accessible à l'utilisateur final.

Dans notre cas, la méthode d'authentification à utiliser est donc la OAuth Implicite Code Flow.

Note

NOTE : J'ai grandement résumé les informations sur OAuth ici, mais si vous souhaitez en apprendre un peu plus, je vous invite à lire cet article de Zeste de Savoir qui me semble une bonne introduction. 👍️

Fonctionnement de l'autorisation implicite

Au final le workflow d'authentification va être très simple et se dérouler de la façon suivante :

  • Notre application est une page web hébergée à l'adresse http://localhost:8000/.
  • Elle va rediriger l'utilisateur sur la page d'authentification fournie par Twitch, située à l'adresse https://id.twitch.tv/oauth2/authorize.
  • Si l'utilisateur autorise bien l'application à accéder à son compte, Twitch le redirigera vers notre application, avec en paramètre dans l'URL, l'access token que l'on devra fournir à chaque appel à une API.
Schémas du workflow d'autorisation implicite OAuth 2.0

Note

NOTE : L'URL sur laquelle on demandera à rediriger l'utilisateur lorsqu'il aura autorisé l'application doit être l'une de celle que l'on avait explicitement autorisée lors de la déclaration de l'application.

Implémentation du workflow d'authentification

Maintenant qu'on sait tout ce que l'on doit savoir à propos de l'authentification, on va mettre ça en pratique. Notre application va se composer de deux fichiers :

  • index.html qui ne va pas contenir grand-chose pour le moment,
  • script.js qui va contenir toute la logique de notre application.

index.html

Commençons par écrire la page HTML :

<!DOCTYPE html>

<html>

    <head>
        <meta charset="UTF-8" />
        <title>Test application</title>
    </head>

    <body>
        <h1>Test application</h1>
        <script src="./script.js"></script>
    </body>

</html>

Je pense qu'elle se passe d'explication, elle ne fait rien de plus que d'appeler le script JavaScript que l'on va écrire ensuite... 😄️

Écriture du fichier script.js

Penchons-nous à présent sur le JavaScript. Pour commencer, on va définir quelques constantes dont nous aurons besoin :

const CLIENT_ID = "0123456789abcdefghijklmnopqrstuvwxyz";
const REDIRECT_URI = "http://localhost:8000/";
const SCOPES = [];

Ensuite, on va écrire quelques helpers, c'est-à-dire des fonctions utilitaires dont on aura besoin dans notre programme :

const helpers = {

    encodeQueryString: function(params) {
        // ...
    },

    decodeQueryString: function(string) {
        // ...
    },

    getUrlParams: function() {
        // ...
    },

};

Je ne vous ai volontairement pas mis leur contenu pour l'instant, car ce n'est pas le sujet de cet article, on va donc juste expliquer ce qu'elles font, et pas comment elles le font :

  • helpers.encodeQueryString() prend en paramètre un objet et le transforme en une string de paramètre utilisable dans une URL :

    {"name": "Truc Muche", "foo": "bar"}  ➡️  "name=Truc+Muche&foo=bar"
    
  • helpers.decodeQueryString() fait exactement l'inverse de la fonction précédente :

    "name=Truc+Muche&foo=bar"  ➡️  {"name": "Truc Muche", "foo": "bar"}
    
  • helpers.getUrlParams() nous fournit un objet contenant les différents paramètres passés dans l'ancre de l'URL (après le #). Si par exemple l'URL de notre page est la suivante :

    http://localhost:8000/#name=Truc+Muche&foo=bar
    

    cette fonction nous retournera :

    {"name": "Truc Muche", "foo": "bar"}
    

À présent travaillons sur l'authentification elle-même. On va commencer par créer une fonction qui se chargera d'effectuer la redirection sur la page d'authentification avec tous les paramètres nécessaires :

const twitch = {

    authentication: function() {
        const params = {
            client_id: CLIENT_ID,
            redirect_uri: REDIRECT_URI,
            response_type: "token",
            scope: SCOPES.join(" "),
        };
        const queryString = helpers.encodeQueryString(params);
        const authenticationUrl = `https://id.twitch.tv/oauth2/authorize?${queryString}`;
        location.href = authenticationUrl;
    },

};

Il ne nous reste plus qu'à écrire une fonction main() qui sera appelée au chargement de la page et qui appellera notre fonction d'authentification :

function main() {
    twitch.authentication();
}

window.onload = main;

Et là, si on lançait notre page telle quelle, on arriverait bien sur la page d'authentification de Twitch... mais une fois l'autorisation accordée, on serait redirigé en boucle ! En effet, dans notre code on appelle inconditionnellement la fonction d'authentification, sans vérifier si on n'est pas déjà authentifiés...

Pour corriger ça, on va ajouter une fonction qui va vérifier si on est déjà authentifiés ou non :

const twitch = {

    isAuthenticated: function() {
        const params = helpers.getUrlParams();
        return params["access_token"] !== undefined;
    },

    // ...

}

Son fonctionnement est simple : la fonction vérifie que le paramètre access_token est bien présent dans l'URL (ce qui est normalement le cas une fois redirigé depuis la page d'authentification de Twitch).

Il ne nous reste plus qu'à modifier la fonction main() de la manière suivante :

function main() {
    if (!twitch.isAuthenticated()) {
        twitch.authentication();
    } else {
        alert("L'utilisateur a bien autorisé l'application !")
    }
}

Le script au complet

Voici donc le script au complet, avec autant d'explications que possible :

// ID de l'application récupéré après l'avoir enregistrée
const CLIENT_ID = "0123456789abcdefghijklmnopqrstuvwxyz";

// Adresse où l'on veut que l'utilisateur soit redirigé après avoir autorisé
// l'application. Cette adresse DOIT être l'une de celles déclarées dans
// l'application sur dev.twitch.tv !!
const REDIRECT_URI = "http://localhost:8000/";

// Liste des éléments auxquels on souhaite accéder...  On reparlera de ça un
// peu plus tard ;)
const SCOPES = [];

// Diverses fonctions utilitaires
const helpers = {

    // Encode un objet sous forme d'une querystring utilisable dans une URL :
    // {"name": "Truc Muche", "foo": "bar"}  ->  "name=Truc+Muche&foo=bar"
    encodeQueryString: function(params) {
        const queryString = new URLSearchParams();
        for (let paramName in params) {
            queryString.append(paramName, params[paramName]);
        }
        return queryString.toString();
    },

    // Décode une querystring sous la forme d'un objet :
    // "name=Truc+Muche&foo=bar"  ->  {"name": "Truc Muche", "foo": "bar"}
    decodeQueryString: function(string) {
        const params = {};
        const queryString = new URLSearchParams(string);
        for (let [paramName, value] of queryString) {
            params[paramName] = value;
        }
        return params;
    },

    // Récupère et décode les paramètres de l'URL
    getUrlParams: function() {
        return helpers.decodeQueryString(location.hash.slice(1));
    },

};

// Fonctions liées à Twitch
const twitch = {

    // Vérifie si l'utilisateur est authentifié ou non
    isAuthenticated: function() {
        const params = helpers.getUrlParams();
        return params["access_token"] !== undefined;
    },

    // Redirige l'utilisateur sur la page d'authentification de Twitch avec les
    // bons paramètres
    authentication: function() {
        const params = {
            client_id: CLIENT_ID,
            redirect_uri: REDIRECT_URI,
            response_type: "token",
            scope: SCOPES.join(" "),
        };
        const queryString = helpers.encodeQueryString(params);
        const authenticationUrl = `https://id.twitch.tv/oauth2/authorize?${queryString}`;
        location.href = authenticationUrl;
    },

};

// Fonction principale
function main() {
    // On lance l'authentification si l'utilisateur n'est pas authentifié
    if (!twitch.isAuthenticated()) {
        twitch.authentication();
    } else {
        alert("L'utilisateur a bien autorisé l'application !")
    }
}

// On appelle la fonction main() lorsque la page a fini de charger
window.onload = main;

Test du workflow d'authentification

On a fini d'écrire le code, il reste maintenant à le tester. Pour ce faire il faut qu'on serve notre page à l'aide d'un serveur Web. Vous pouvez utiliser ce que vous voulez ici : Nginx, Apache,...

Pour ma part, je vais servir ma page avec le serveur Web intégré au module http de Python 3 :

python3 -m http.server 8000

Cette commande, si elle est exécutée depuis le dossier contenant mes fichiers index.html et script.js va me permettre d'accéder à mon application à l'adresse http://localhost:8000/ (qui est donc l'adresse de redirection que j'avais explicitement listée lors de la déclaration de l'application).

Si tout se passe bien vous devriez voir une page semblable à celle-ci :

Capture d'écran : authorisation OAuth Twitch

Puis, de retours sur l'application, vous devriez voir le message « L'utilisateur a bien autorisé l'application ! » apparaitre dans une fenêtre d'alerte JavaScript.

Capture d'écran : message affiché en cas de succès

C'est tout pour aujourd'hui !

Maintenant, vous avez comment se passe la phase d'authentification auprès des APIs de Twitch, la prochaine fois on va donc pouvoir passer aux choses intéressantes : appeler les APIs Twitch et afficher nos premières notifications !

N'hésitez pas à regarder la vidéo de la session de live coding que j'avais faite sur ce sujet, elle est disponible sur YouTube :

Le code complet de la phase d'authentification est, comme d'habitude disponible sur Github :

N'hésitez pas à poser vos questions en commentaire ou sur Discord, et je vous donne rendez-vous prochainement pour la suite ! 😃️