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 tatsächlich die Person ist, die 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 hingegen durch die Implementierung von VAPID erreicht werden. Das bedeutet, dass deine Web-App keine „gcm_sender_id“ mehr benötigt.

In diesem Artikel werde ich zuerst beschreiben, wie Sie Ihren vorhandenen Servercode konvertieren, um das Web Push Protocol mit FCM zu verwenden. Als Nächstes zeige ich Ihnen, wie Sie VAPID in 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 Daten über Ihre Webanwendung an den Nutzer zu senden. In Chrome erhalten Sie einen FCM-Endpunkt, wenn Sie einen Nutzer ohne VAPID abonnieren. (Weitere Informationen zu VAPID finden Sie später.) 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 mit der Adresse https://android.googleapis.com/gcm/send/ABCD1234 hätte 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 prüfen, ob unser Servercode den FCM-Endpunkt richtig verarbeitet. Unten finden Sie ein Beispiel für eine Anfrage an einen Push-Dienst in Node. Beachten Sie, dass wir den API-Schlüssel für FCM den Anfrageheadern hinzufügen. Für andere Push-Dienstendpunkte ist dies nicht erforderlich. Für Chrome vor Version 52, Opera Android und den Samsung-Browser müssen Sie 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.

Jetzt neu: 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 vermeiden. 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 Paar aus öffentlichem und privatem Schlüssel. Der öffentliche Schlüssel wird Ihrer 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.

Paar aus öffentlichem/privatem Schlüssel 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 SOLLTEN ein Signaturschlüsselpaar generieren und verwalten, das über die P-256-Kurve mit einer digitalen Signatur mit elliptischer Kurve (Elliptic Curve Digital Signature, ECDSA) verwendet werden kann.

Informationen dazu finden Sie in der Web-Push-Knotenbibliothek:

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 es funktioniert hat, erfährst du, wenn du den Endpunkt im resultierenden Aboobjekt testest. Wenn der Ursprung fcm.googleapis.com ist, funktioniert es.

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 mit einem einzelnen Punkt verbunden 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"
}

Diese wird dann mit der base64-URL 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 die Zielgruppe wie folgt 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. Erstens enthält der Autorisierungsheader buchstäblich das Wort „WebPush“, gefolgt von einem Leerzeichen und dem JWT. Beachten Sie auch die Punkte, die JWT-Header, Nutzlast und Signatur 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 GCM-Konto registrieren, um Push in Chrome zu verwenden. Sie können in Chrome und Firefox denselben Codepfad verwenden, um einen Nutzer zu abonnieren und eine Nachricht an einen Nutzer zu senden. Beide folgen 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 einen Ausgleich 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.