web-dev-qa-db-fra.com

En-tête d'autorisation HTTP dans EventSource (événements envoyés par le serveur)

Je dois définir un en-tête d'autorisation sur un événement HTML5 Source. Comme Server Server Events semble ne plus être utilisé depuis l'apparition de Websockets, je ne trouve aucune documentation utile. L'approche que j'ai déjà trouvée consiste à transmettre les données d'autorisation dans l'URL ... mais je n'aime pas cette méthode.

J'utilise AngularJS et les intercepteurs sont définis sur $ httpProvider, mais EventSource n'est pas intercepté par AngularJS. Je ne peux donc pas ajouter d'en-tête.

25

EventSource ne dispose pas d'un API pour l'envoi d'en-têtes HTTP au serveur. Je me débattais aussi avec ce problème lorsque je construisais une discussion en temps réel avec SSE.

Cependant, je pense que les cookies seront envoyés automatiquement si votre serveur SSE est le même serveur que votre serveur d'authentification.

9
srigi

Je sais que votre message a été publié il y a plus d'un an, mais je me suis retrouvé dans le même bateau avec de bonnes réponses. J'espère que cela pourra aider quelqu'un, ou au moins lui donner quelques idées ...

Les cookies semblent assez faciles, mais que se passe-t-il si quelqu'un les bloque? Je devrais les inviter à autoriser les cookies à utiliser le site. À ce stade, ils commencent à se demander s'ils peuvent faire confiance au site car ils ont désactivé les cookies pour des "raisons de sécurité". Pendant tout ce temps, je veux que les cookies soient activés pour des raisons de sécurité!

Avec AJAX, on peut facilement POST authentifier des données via SSL, mais ce n'est tout simplement pas possible avec SSE. J'ai vu de nombreux articles dans lesquels on disait ensuite: "utilisez simplement la chaîne de requête", mais je ne veux pas compromettre la sécurité d'un client en envoyant les données d'authentification en texte brut (example.com/stream?sessionID=idvalue). pourrait snoop.

Après avoir creusé mon cerveau pendant quelques heures, j'ai réalisé que je pouvais atteindre l'objectif global sans compromettre les données d'authentification du client. Juste pour clarifier les choses, je n’ai pas trouvé de moyen de POST lors de l’établissement d’une connexion EventSource, mais cela permet au navigateur de transmettre en toute sécurité un jeton d’authentification avec EventSource chaque fois qu’il se reconnecte. Leur clé est d’obtenir le jeton/ID de session souhaité dans le lastEventID.

L'utilisateur peut s'authentifier comme d'habitude avec un nom d'utilisateur/mot de passe (ou par AJAX en postant un jeton que vous conservez dans le stockage local). Le processus d'authentification AJAX renverra un objet JSON avec un jeton de courte durée (expire au bout de 60 secondes ou lorsqu'il est utilisé), qui serait sauvegardé dans le backend souhaité (par exemple: mySQL) avec un jeton. A ce stade, vous établissez votre connexion SSE de la manière suivante:

    qString = "?slt=" + "value-that-expires-within-seconds";
    streamURL = "http://example.com/stream.php";
    var streamSource = new EventSource(streamURL + qString);

    streamSource.addEventListener('auth',function(e) {
        var authStatus = JSON.parse(e.data);
        if (authStatus.session !== 'valid') {
            qString = "";
            streamSource.close();
        }
    })

Dans le PHP correspondant, vous feriez quelque chose comme ceci:

        header("Content-Type: text/event-stream\n");
        ob_end_flush();
        ob_start();

        if (isThisShortLivedTokenValid($_GET["slt"])) {
            // The short-lived-token is still valid... so we will lookup
            // the value of the corresponding longer-lasting token and
            // IMMEDIATELY invalidate the short-lived-token in the db.
            sendMsg($realToken,'auth','session','valid');
            exit;
        } else if (isThisRealTokenValid($_SERVER["HTTP_LAST_EVENT_ID"])){
            while (1) {
                // normal code goes here
                // if ($someCondition == 'newDataAvailable') sendMsg($realToken,'chat','msg-id','msg-content');
            }
        } else {
            http_response_code(404); // stop the browser from reconnecting.
            exit; //quit the PHP script and don't send anything.
        }


        function sendMsg($id, $event, $key, $val) {
            echo "{" . PHP_EOL;
            echo "event: " . $event . PHP_EOL;
            echo "id: $id" . PHP_EOL;
            echo 'data: {"' . $key . '" : "' . $val . '"}' . PHP_EOL;
            echo "}" . PHP_EOL;
            echo PHP_EOL;
            ob_flush();
            flush();
        }

        function isThisShortLivedTokenValid($sltValue) {
            //stuff to connect to DB and determine if the
            //value is still valid for authentication
            return $dbResult == $sltValue ? TRUE : FALSE;
        }

SSE se connecte avec le jeton de courte durée, PHP valide contre le jeton de courte durée et le supprime de la base de données afin qu'il ne puisse plus jamais utiliser AUTH. Ceci est un peu similaire lorsque vous envoyez par SMS un code à 6 chiffres pour vous connecter à la banque en ligne. Nous utilisons PHP pour envoyer le jeton REAL (qui expire beaucoup plus tard) que nous avons extrait de la base de données en tant qu'ID d'événement. Il n'est pas vraiment nécessaire que Javascript fasse quoi que ce soit avec cet événement - le serveur mettra automatiquement fin à la connexion, mais vous pouvez l'écouter si vous voulez en faire plus.

À ce stade, la connexion SSE est terminée depuis que PHP a terminé le script. Cependant, le navigateur rétablira automatiquement la connexion (généralement avec 3 secondes). Cette fois, il enverra le lastEventId ... que nous avons défini sur la valeur du jeton avant que la connexion ne soit interrompue. Lors de la prochaine connexion, cette valeur sera utilisée comme notre jeton et l'application s'exécutera comme prévu. Il n'est pas vraiment nécessaire de supprimer la connexion tant que vous commencez à utiliser le véritable jeton comme ID d'événement lorsque vous envoyez des messages/événements. Cette valeur de jeton est transmise complètement chiffrée via SSL à la fois lorsque le navigateur le reçoit et lors de chaque connexion ultérieure au serveur. La valeur transmise "en clair" a expiré quelques secondes après sa réception et son utilisation et ne peut plus être utilisée par ceux qui la découvrent. Si quelqu'un tente de l'utiliser, il recevra une 404 RESPONSE.

Si vous utilisez déjà l'ID de flux d'événements à d'autres fins, il est possible que cela ne fonctionne pas immédiatement si vous ne concaténez pas le jeton d'authentification et la valeur précédemment utilisée, puis que vous le divisez en variables afin qu'il soit transparent pour le reste du fichier. app. Quelque chose comme:

    // when sending data, send both values
    $sseID = $token_value . "_" . $previouslyUsedID;
    sendMsg($sseID,'chat','msg-id','msg-content');

    // when a new connection is established, break apart the values
    $manyIDs = explode("_", $_SERVER["HTTP_LAST_EVENT_ID"])
    $token_value = $manyIDs[0]
    $previouslyUsedID = $manyIDs[1]
7
KeithMcFly

Ce polyfill ajoute le support de l'en-tête d'autorisation: https://github.com/Yaffle/EventSource/

Alors tu peux faire:

new EventSource("https://domain/stream", { authorizationHeader: "Bearer ..." });
7
rafaelzlisboa

l'autre façon de transmettre le jeton d'authentification consiste à utiliser l'URL en tant que paramètre de requête, mais vous devez prendre en compte la sécurité. Ajoutez également le support de l'autorisation via le paramètre de requête du côté serveur.

2
Sergey Leyko

Le window.EventSource ne semble pas encore supporter de passer des en-têtes supplémentaires. La bonne nouvelle est qu'il existe d'autres implémentations de EventSource qui prennent en charge des en-têtes supplémentaires. Certains d'entre eux sont les suivants:

const eventSource = new EventSource(resoureUrl, {
            headers: {
                'Authorization': 'Bearer ' + authorizationToken;
            }
        });

es.onmessage = result => {
    const data = JSON.parse(result.data);
    console.log('Data: ', data);
};

es.onerror = err => {
    console.log('EventSource error: ', err);
};
0
Manoj Shrestha