Web Push Interoperability gewinnt

Matt Gaunt
Joe Medley
Joe Medley

Bei der ersten Unterstützung der Web Push API in Chrome stützte es sich dabei auf den Push-Dienst Firebase Cloud Messaging (FCM), früher Google Cloud Messaging (GCM). Dazu musste eine proprietäre API verwendet werden. Auf diese Weise konnte Chrome die Web Push API Entwicklern zu einem Zeitpunkt zur Verfügung stellen, als die Web Push Protocol-Spezifikation noch geschrieben wurde, und später eine Authentifizierung ermöglichte, d. h., der Absender der Nachricht ist der, der er vorgibt, zu einem Zeitpunkt, als das Web Push Protocol noch nicht verfügbar war. Gute Neuigkeiten: Keiner dieser Fälle trifft zu.

FCM / GCM und Chrome unterstützen jetzt das standardmäßige Web Push Protocol. Die Absenderauthentifizierung kann hingegen durch die Implementierung von VAPID erreicht werden, d. h., deine Web-App benötigt keine „gcm_sender_id“ mehr.

In diesem Artikel werde ich zuerst beschreiben, wie Sie Ihren vorhandenen Servercode so konvertieren, dass er das Web Push Protocol mit FCM verwendet. Als Nächstes zeige ich Ihnen, wie Sie VAPID im Client- und Servercode implementieren.

FCM unterstützt Web Push Protocol

Beginnen wir mit ein wenig Kontext. Wenn sich Ihre Webanwendung für ein Push-Abo registriert, erhält sie die URL eines Push-Dienstes. Ihr Server verwendet diesen Endpunkt, um über Ihre Webanwendung Daten an Ihren Nutzer zu senden. In Chrome erhalten Sie einen FCM-Endpunkt, wenn Sie einen Nutzer ohne VAPID abonnieren. Zu VAPID kommen wir später noch. Bevor das Web-Push-Protokoll von FCM unterstützt wurde, mussten Sie die FCM-Registrierungs-ID aus dem Ende der URL extrahieren und in den Header einfügen, bevor Sie eine FCM API-Anfrage stellen. Der FCM-Endpunkt https://android.googleapis.com/gcm/send/ABCD1234 hat beispielsweise die Registrierungs-ID „ABCD1234“.

Da FCM jetzt das Web-Push-Protokoll unterstützt, können Sie den Endpunkt intakt lassen und die URL als Web-Push-Protokoll-Endpunkt verwenden. Damit reflektieren wir sie mit Firefox und hoffentlich auch allen anderen zukünftigen Browsern.

Bevor wir uns mit VAPID befassen, müssen wir prüfen, ob unser Servercode den FCM-Endpunkt richtig verarbeitet. Im Folgenden finden Sie ein Beispiel für eine Anfrage an einen Push-Dienst in Node. Für FCM wird der API-Schlüssel den Anfrageheadern hinzugefügt. Für andere Push-Dienstendpunkte ist dies nicht erforderlich. Für Chrome vor Version 52, Opera Android und den Samsung-Browser müssen Sie außerdem eine "gcm_sender_id" in die Datei "manifest.json" Ihrer Webanwendung einfügen. Mit dem API-Schlüssel und der Absender-ID wird geprüft, ob der Server, der die Anfragen stellt, tatsächlich Nachrichten an den Empfänger senden darf.

const headers = new Headers();
// 12-hour notification time to live.
headers.append('TTL', 12 * 60 * 60);
// Assuming no data is going to be sent
headers.append('Content-Length', 0);

// Assuming you're not using VAPID (read on), this
// proprietary header is needed
if(subscription.endpoint
    .indexOf('https://android.googleapis.com/gcm/send/') === 0) {
    headers.append('Authorization', 'GCM_API_KEY');
}

fetch(subscription.endpoint, {
    method: 'POST',
    headers: headers
})
.then(response => {
    if (response.status !== 201) {
    throw new Error('Unable to send push message');
    }
});

Beachte, dass dies eine Änderung an der FCM / GCM-API ist. Du musst deine Abos nicht aktualisieren. Ändere einfach deinen Servercode, um die Header wie oben gezeigt zu definieren.

Jetzt neu: VAPID zur Serveridentifikation

VAPID ist der neue Kurzname für Voluntary Application Server Identification. Diese neue Spezifikation definiert im Wesentlichen einen Handshake zwischen Ihrem Anwendungsserver und dem Push-Dienst und ermöglicht dem Push-Dienst, zu bestätigen, welche Website Nachrichten sendet. Mit VAPID können Sie die FCM-spezifischen Schritte zum Senden von Push-Nachrichten vermeiden. Sie benötigen kein Firebase-Projekt, einen gcm_sender_id- oder Authorization-Header mehr.

Der Vorgang ist ziemlich einfach:

  1. Ihr Anwendungsserver erstellt ein öffentliches/privates Schlüsselpaar. Der öffentliche Schlüssel wird Ihrer Webanwendung zugewiesen.
  2. Wenn sich der Nutzer für den Empfang von Push-Anfragen entscheidet, fügen Sie den öffentlichen Schlüssel zum „options“-Objekt des „subscribe()“-Aufrufs hinzu.
  3. Wenn Ihr Anwendungsserver eine Push-Nachricht sendet, fügen Sie zusammen mit dem öffentlichen Schlüssel ein signiertes JSON-Webtoken hinzu.

Sehen wir uns diese Schritte einmal genauer an.

Paar aus öffentlichem/privatem Schlüssel erstellen

Ich bin schlecht bei der Verschlüsselung. Hier ist der relevante Abschnitt aus der Spezifikation zum Format der öffentlichen/privaten VAPID-Schlüssel:

Anwendungsserver müssen ein Signaturschlüsselpaar generieren und verwalten, das mit der digitalen Signatur mit elliptischer Kurve (Elliptic Curve Digital Signature, ECDSA) über die P-256-Kurve verwendet werden kann.

Wie das geht, sehen Sie in der Web-Push-Knotenbibliothek:

function generateVAPIDKeys() {
    var curve = crypto.createECDH('prime256v1');
    curve.generateKeys();

    return {
    publicKey: curve.getPublicKey(),
    privateKey: curve.getPrivateKey(),
    };
}

Öffentlichen Schlüssel abonnieren

Um einen Chrome-Nutzer für Push mit dem öffentlichen VAPID-Schlüssel zu abonnieren, musst du den öffentlichen Schlüssel als Uint8Array übergeben. Dazu wird der Parameter applicationServerKey der Methode "subscribe()" verwendet.

const publicKey = new Uint8Array([0x4, 0x37, 0x77, 0xfe, …. ]);
serviceWorkerRegistration.pushManager.subscribe(
    {
    userVisibleOnly: true,
    applicationServerKey: publicKey
    }
);

Wenn Sie den Endpunkt im resultierenden Aboobjekt untersuchen, wissen Sie, ob es funktioniert hat. Wenn der Ursprung fcm.googleapis.com ist, funktioniert es.

https://fcm.googleapis.com/fcm/send/ABCD1234

Push-Nachrichten senden

Um eine Nachricht mit VAPID zu senden, müssen Sie eine normale Web-Push-Protokoll-Anfrage mit zwei zusätzlichen HTTP-Headern stellen: einem Autorisierungsheader und einem Crypto-Key-Header.

Autorisierungsheader

Der Header Authorization ist ein signiertes JSON Web Token (JWT) mit vorangestelltem „WebPush“.

Ein JWT ist eine Möglichkeit, ein JSON-Objekt so für eine zweite Partei freizugeben, dass die sendende Partei es signieren kann und die empfangende Partei überprüfen kann, ob die Signatur vom erwarteten Absender stammt. Die Struktur eines JWT besteht aus drei verschlüsselten Strings, die durch einen einzelnen Punkt dazwischen verbunden sind.

<JWTHeader>.<Payload>.<Signature>

JWT-Header

Der JWT-Header enthält den zum Signieren verwendeten Algorithmusnamen und den Tokentyp. Für VAPID muss dies so lauten:

{
    "typ": "JWT",
    "alg": "ES256"
}

Dies wird dann base64-URL-codiert und bildet den ersten Teil des JWT.

Nutzlast

Die Nutzlast ist ein weiteres JSON-Objekt, das Folgendes enthält:

  • Zielgruppe („aud“)
    • Dies ist der Ursprung des Push-Dienstes, NICHT der Ursprung Ihrer Website. In JavaScript könnten Sie das tun, um die Zielgruppe abzurufen: const audience = new URL(subscription.endpoint).origin
  • Ablaufzeit („exp“)
    • Dies ist die Anzahl der Sekunden, bis die Anfrage als abgelaufen angesehen werden sollte. Dies MÜSSEN innerhalb von 24 Stunden nach der Anfrage in UTC erfolgen.
  • Betreff („Unterüberschrift“)
    • Der Betreff muss eine URL oder eine mailto:-URL sein. Dies stellt einen Ansprechpartner für den Fall dar, dass der Push-Dienst den Absender der Nachricht kontaktieren muss.

Eine Beispielnutzlast könnte so aussehen:

{
    "aud": "http://push-service.example.com",
    "exp": Math.floor((Date.now() / 1000) + (12 * 60 * 60)),
    "sub": "mailto: my-email@some-url.com"
}

Dieses JSON-Objekt ist base64-URL-codiert und bildet den zweiten Teil des JWT.

Unterschrift

Die Signatur ist das Ergebnis der Verknüpfung von codiertem Header und Nutzlast mit einem Punkt und anschließender Verschlüsselung des Ergebnisses mit dem zuvor erstellten privaten VAPID-Schlüssel. Das Ergebnis selbst sollte mit einem Punkt an den Header angehängt werden.

Ich werde kein Codebeispiel hierfür zeigen, da es eine Reihe von Bibliotheken gibt, die den Header und die Nutzlast-JSON-Objekte verwenden und diese Signatur für Sie generieren.

Das signierte JWT wird als Autorisierungsheader mit vorangestelltem „WebPush“ verwendet und sieht in etwa so aus:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Beachten Sie dabei ein paar Dinge. Der Autorisierungs-Header enthält buchstäblich das Wort „WebPush“, gefolgt von einem Leerzeichen und dem JWT. Beachten Sie auch die Punkte, die den JWT-Header, die Nutzlast und die Signatur trennen.

Header für Crypto-Schlüssel

Neben dem Autorisierungsheader müssen Sie dem Crypto-Key-Header den öffentlichen VAPID-Schlüssel als base64-URL-codierten String mit vorangestelltem p256ecdsa= hinzufügen.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

Wenn Sie eine Benachrichtigung mit verschlüsselten Daten senden, verwenden Sie bereits den Crypto-Key-Header. Sie müssen also zum Hinzufügen des Anwendungsserverschlüssels nur ein Semikolon hinzufügen, bevor Sie den obigen Inhalt einfügen. Dies führt zu folgendem Ergebnis:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

Wirklichkeit dieser Änderungen

Mit VAPID müssen Sie sich nicht mehr für ein Konto bei GCM registrieren, um Push in Chrome zu verwenden. Sie können denselben Codepfad verwenden, um einen Nutzer zu abonnieren und eine Nachricht an einen Nutzer in Chrome und Firefox zu senden. Beide entsprechen den Standards.

Beachten Sie, dass Sie in Chrome 51 und früheren Versionen, Opera für Android und Samsung-Browsern weiterhin gcm_sender_id in Ihrem Web-App-Manifest definieren müssen. Außerdem müssen Sie den Autorisierungsheader dem zurückgegebenen FCM-Endpunkt hinzufügen.

VAPID ist eine Alternative zu diesen proprietären Anforderungen. VAPID funktioniert in allen Browsern, die Web-Push unterstützen. Je mehr Browser VAPID unterstützen, desto können Sie entscheiden, wann gcm_sender_id aus Ihrem Manifest entfernt werden soll.