Web Push Interoperability gewinnt

Matt Gaunt
Joe Medley
Joe Medley

Als Chrome die Web Push API zum ersten Mal unterstützte, nutzte es den Push-Dienst Firebase Cloud Messaging (FCM), früher Google Cloud Messaging (GCM). Dazu musste die proprietäre API verwendet werden. So konnte Chrome die Web Push API für Entwickler verfügbar machen, als die Spezifikation des Web Push-Protokolls noch geschrieben wurde, und später die Authentifizierung (d. h., dass der Absender der Nachricht der ist, der er vorgibt zu sein) bereitstellen, als diese im Web Push-Protokoll noch fehlte. Die gute Nachricht: Das ist nicht mehr der Fall.

FCM / GCM und Chrome unterstützen jetzt das standardmäßige Web Push Protocol. Die Absenderauthentifizierung kann durch Implementierung von VAPID erfolgen. Das bedeutet, dass für Ihre Webanwendung keine „gcm_sender_id“ mehr erforderlich ist.

In diesem Artikel wird zuerst beschrieben, wie Sie Ihren vorhandenen Servercode so konvertieren, dass das Web Push Protocol mit FCM verwendet wird. Als Nächstes zeige ich dir, wie du VAPID sowohl in deinem Client- als auch in deinem Servercode implementierst.

FCM unterstützt das Web Push-Protokoll

Fangen wir mit ein bisschen Kontext an. Wenn Ihre Webanwendung für ein Push-Abo registriert wird, erhält sie die URL eines Push-Dienstes. Ihr Server verwendet diesen Endpunkt, um Daten über Ihre Webanwendung an den Nutzer zu senden. In Chrome erhalten Sie einen FCM-Endpunkt, wenn Sie einen Nutzer ohne VAPID abonnieren. (Wir werden VAPID später behandeln.) Bevor FCM das Web Push-Protokoll unterstützte, mussten Sie die FCM-Registrierungs-ID am Ende der URL extrahieren und in die Kopfzeile einfügen, bevor Sie eine FCM API-Anfrage senden konnten. Ein FCM-Endpunkt von 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 unverändert lassen und die URL als Web Push-Protokoll-Endpunkt verwenden. (Das entspricht Firefox und hoffentlich auch allen anderen zukünftigen Browsern.)

Bevor wir uns mit VAPID befassen, müssen wir dafür sorgen, dass unser Servercode den FCM-Endpunkt richtig verarbeitet. Unten sehen Sie ein Beispiel für eine Anfrage an einen Push-Dienst in Node. Für FCM fügen wir den API-Schlüssel den Anfrageheadern hinzu. 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 weiterhin eine „gcm_sender_id“ in das Manifest.json Ihrer Webanwendung einfügen. Anhand des API-Schlüssels 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');
    }
});

Denken Sie daran, dass dies eine Änderung an der FCM-/GCM-API ist. Sie müssen Ihre Abos also nicht aktualisieren, sondern nur Ihren Servercode ändern, um die Header wie oben gezeigt zu definieren.

Einführung von VAPID für die Serveridentifikation

VAPID ist der coole neue Kurzname für Voluntary Application Server Identification (freiwillige Identifikation von Anwendungsservern). Diese neue Spezifikation definiert im Wesentlichen einen Handshake zwischen Ihrem App-Server und dem Push-Dienst und ermöglicht es dem Push-Dienst, zu bestätigen, welche Website Nachrichten sendet. Mit VAPID können Sie die FCM-spezifischen Schritte zum Senden einer Push-Nachricht umgehen. Du benötigst kein Firebase-Projekt, keinen gcm_sender_id-Header und keinen Authorization-Header mehr.

Der Vorgang ist ganz einfach:

  1. Ihr Anwendungsserver erstellt ein öffentliches/privates Schlüsselpaar. Der öffentliche Schlüssel wird an Ihre Webanwendung übergeben.
  2. Wenn der Nutzer Push-Benachrichtigungen erhalten möchte, fügen Sie den öffentlichen Schlüssel dem Optionsobjekt des subscribe()-Aufrufs hinzu.
  3. Wenn Ihr App-Server eine Push-Nachricht sendet, fügen Sie zusammen mit dem öffentlichen Schlüssel ein signiertes JSON Web Token hinzu.

Sehen wir uns diese Schritte genauer an.

Öffentliches/privates Schlüsselpaar erstellen

Ich bin nicht gut in Verschlüsselung, daher findest du hier den relevanten Abschnitt 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.

In der Web-Push-Node-Bibliothek wird gezeigt, wie das geht:

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

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

Mit dem öffentlichen Schlüssel abonnieren

Wenn Sie einen Chrome-Nutzer mit dem VAPID-öffentlichen Schlüssel für Push-Benachrichtigungen abonnieren möchten, müssen Sie den öffentlichen Schlüssel als Uint8Array mit dem Parameter applicationServerKey der subscribe()-Methode übergeben.

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

Ob die Änderung funktioniert hat, erkennst du am Endpunkt im resultierenden Aboobjekt. Wenn der Ursprung fcm.googleapis.com ist, funktioniert die Änderung.

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

Push-Nachricht senden

Wenn Sie eine Nachricht mit VAPID senden möchten, müssen Sie eine normale Web Push Protocol-Anfrage mit zwei zusätzlichen HTTP-Headern senden: einem Autorisierungsheader und einem Crypto-Key-Header.

Autorisierungsheader

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

Mit einem JWT können Sie ein JSON-Objekt so für eine zweite Partei freigeben, dass die sendende Partei es signieren 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 voneinander getrennt sind.

<JWTHeader>.<Payload>.<Signature>

JWT-Header

Der JWT-Header enthält den Namen des Algorithmus, der für die Signatur verwendet wird, und den Tokentyp. Für VAPID gilt Folgendes:

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

Dieser wird dann base64url-codiert und bildet den ersten Teil des JWT.

Nutzlast

Die Nutzlast ist ein weiteres JSON-Objekt mit folgenden Elementen:

  • „Zielgruppe“ („aud“)
    • Dies ist der Ursprung des Push-Dienstes (NICHT der Ursprung Ihrer Website). In JavaScript können Sie so die Zielgruppe abrufen: const audience = new URL(subscription.endpoint).origin
  • Ablaufzeit („exp“)
    • Das ist die Anzahl der Sekunden, nach denen die Anfrage als abgelaufen betrachtet werden soll. Dies muss INNERHALB VON 24 STUNDEN nach der Anfrage erfolgen, in UTC.
  • Betreff („sub“)
    • Der Betreff muss eine URL oder eine mailto:-URL sein. Dies ist ein Ansprechpartner, falls 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 base64url-codiert und bildet den zweiten Teil des JWT.

Unterschrift

Die Signatur ist das Ergebnis der Verbindung des codierten Headers und der Nutzlast mit einem Punkt und der anschließenden Verschlüsselung des Ergebnisses mit dem zuvor erstellten VAPID-privaten Schlüssel. Das Ergebnis selbst sollte dem Header mit einem Punkt angehängt werden.

Ich zeige hier kein Codebeispiel, da es eine Reihe von Bibliotheken gibt, die die Header- und Nutzlast-JSON-Objekte übernehmen und diese Signatur für Sie generieren.

Das signierte JWT wird als Autorisierungsheader verwendet, vorangestellt mit „WebPush“. Es sieht in etwa so aus:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Beachten Sie Folgendes: Der Autorisierungsheader enthält zuerst das Wort „WebPush“ und sollte dann von einem Leerzeichen gefolgt vom JWT gefolgt werden. Beachten Sie auch die Punkte, die den JWT-Header, die Nutzlast und die Signatur voneinander trennen.

Crypto-Key-Header

Zusätzlich zum Autorisierungsheader musst du dem Crypto-Key-Header deinen VAPID-öffentlichen 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. Um den Schlüssel für den Anwendungsserver hinzuzufügen, müssen Sie vor dem Hinzufügen des obigen Inhalts nur ein Semikolon einfügen. Das Ergebnis sieht dann so aus:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

Die Realität dieser Änderungen

Mit VAPID müssen Sie sich nicht mehr für ein Konto bei GCM registrieren, um Push-Mitteilungen in Chrome zu verwenden. Außerdem können Sie denselben Codepfad verwenden, um Nutzer zu abonnieren und ihnen sowohl in Chrome als auch in Firefox Nachrichten zu senden. Beide entsprechen den Standards.

Beachten Sie, dass Sie in Chrome 51 und niedriger, in Opera für Android und im Samsung-Browser die gcm_sender_id weiterhin in Ihrem Web-App-Manifest definieren und dem zurückgegebenen FCM-Endpunkt den Autorisierungsheader hinzufügen müssen.

VAPID bietet eine Alternative zu diesen proprietären Anforderungen. Wenn Sie VAPID implementieren, funktioniert es in allen Browsern, die Web-Push unterstützen. Wenn immer mehr Browser VAPID unterstützen, kannst du entscheiden, wann du die gcm_sender_id aus deinem Manifest entfernst.

an.