Web Push-interoperabiliteit wint

Toen Chrome voor het eerst de Web Push API ondersteunde, vertrouwde het op de pushservice Firebase Cloud Messaging (FCM), voorheen bekend als Google Cloud Messaging (GCM). Dit vereiste het gebruik van de eigen API. Hierdoor kon Chrome de Web Push API beschikbaar maken voor ontwikkelaars in een tijd dat de Web Push Protocol-specificatie nog werd geschreven en later authenticatie bieden (wat betekent dat de afzender van het bericht is wie hij zegt dat hij is) in een tijd waarin het Web Push Protocol ontbrak het. Goed nieuws: geen van beide is meer waar.

FCM/GCM en Chrome ondersteunen nu het standaard Web Push Protocol , terwijl afzenderauthenticatie kan worden bereikt door VAPID te implementeren, wat betekent dat uw webapp niet langer een 'gcm_sender_id' nodig heeft.

In dit artikel ga ik eerst beschrijven hoe u uw bestaande servercode kunt converteren om het Web Push Protocol met FCM te gebruiken. Vervolgens laat ik u zien hoe u VAPID in zowel uw client- als servercode implementeert.

FCM ondersteunt het Web Push-protocol

Laten we beginnen met een beetje context. Wanneer uw webapplicatie zich aanmeldt voor een push-abonnement, krijgt deze de URL van een push-service. Uw server gebruikt dit eindpunt om via uw webapp gegevens naar uw gebruiker te verzenden. In Chrome krijgt u een FCM-eindpunt als u zich abonneert op een gebruiker zonder VAPID. (We zullen VAPID later bespreken). Voordat FCM het Web Push Protocol ondersteunde, moest u de FCM-registratie-ID uit het einde van de URL halen en in de header plaatsen voordat u een FCM API-verzoek deed. Een FCM-eindpunt https://android.googleapis.com/gcm/send/ABCD1234 heeft bijvoorbeeld de registratie-ID 'ABCD1234'.

Nu FCM het Web Push Protocol ondersteunt, kunt u het eindpunt intact laten en de URL gebruiken als een Web Push Protocol-eindpunt. (Dit brengt het in lijn met Firefox en hopelijk elke andere toekomstige browser.)

Voordat we in VAPID duiken, moeten we ervoor zorgen dat onze servercode het FCM-eindpunt correct verwerkt. Hieronder ziet u een voorbeeld van het indienen van een aanvraag bij een pushservice in Node. Merk op dat we voor FCM de API-sleutel toevoegen aan de verzoekheaders. Voor andere push-service-eindpunten is dit niet nodig. Voor Chrome vóór versie 52, Opera Android en de Samsung Browser moet u ook nog steeds een 'gcm_sender_id' opnemen in het manifest.json van uw webapp. Aan de hand van de API-sleutel en het afzender-ID wordt gecontroleerd of de server die de verzoeken doet, daadwerkelijk berichten naar de ontvangende gebruiker mag sturen.

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');
    }
});

Houd er rekening mee dat dit een wijziging is in de API van FCM/GCM, dus u hoeft uw abonnementen niet bij te werken. Wijzig gewoon uw servercode om de headers te definiëren, zoals hierboven weergegeven.

Introductie van VAPID voor serveridentificatie

VAPID is de coole nieuwe korte naam voor " Voluntary Application Server Identification ". Deze nieuwe specificatie definieert in wezen een handshake tussen uw app-server en de push-service en zorgt ervoor dat de push-service kan bevestigen welke site berichten verzendt. Met VAPID vermijdt u de FCM-specifieke stappen voor het versturen van een pushbericht. U heeft niet langer een Firebase-project, een gcm_sender_id of een Authorization header nodig.

Het proces is vrij eenvoudig:

  1. Uw toepassingsserver maakt een openbaar/privaat sleutelpaar aan. De openbare sleutel wordt aan uw web-app gegeven.
  2. Wanneer de gebruiker ervoor kiest om pushes te ontvangen, voegt u de openbare sleutel toe aan het optieobject van de abonneer()-oproep.
  3. Wanneer uw app-server een pushbericht verzendt, voegt u een ondertekend JSON-webtoken toe samen met de openbare sleutel.

Laten we deze stappen in detail bekijken.

Maak een openbaar/privaat sleutelpaar

Ik ben verschrikkelijk in encryptie, dus hier is het relevante gedeelte uit de specificatie met betrekking tot het formaat van de openbare/privésleutels van VAPID:

Applicatieservers MOETEN een ondertekeningssleutelpaar genereren en onderhouden dat bruikbaar is met een elliptische curve digitale handtekening (ECDSA) over de P-256-curve.

Hoe u dit doet, kunt u zien in de web-push node-bibliotheek :

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

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

Abonneren met de publieke sleutel

Als u een Chrome-gebruiker wilt abonneren op push met de openbare VAPID-sleutel, moet u de openbare sleutel doorgeven als een Uint8Array met behulp van de applicationServerKey parameter van de methode Subscribe().

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

U weet of het heeft gewerkt door het eindpunt in het resulterende abonnementsobject te onderzoeken. Als de oorsprong fcm.googleapis.com is, werkt het.

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

Een pushbericht verzenden

Om een ​​bericht te verzenden met VAPID, moet u een normaal Web Push Protocol-verzoek indienen met twee extra HTTP-headers: een Authorization-header en een Crypto-Key-header.

Autorisatiekop

De Authorization header is een ondertekend JSON Web Token (JWT) met 'WebPush' ervoor.

Een JWT is een manier om een ​​JSON-object te delen met een tweede partij, zodat de verzendende partij het kan ondertekenen en de ontvangende partij kan verifiëren dat de handtekening van de verwachte afzender komt. De structuur van een JWT bestaat uit drie gecodeerde strings, samengevoegd met een enkele punt ertussen.

<JWTHeader>.<Payload>.<Signature>

JWT-koptekst

De JWT-header bevat de algoritmenaam die wordt gebruikt voor ondertekening en het type token. Voor VAPID moet dit zijn:

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

Dit wordt vervolgens met base64-URL gecodeerd en vormt het eerste deel van de JWT.

Laadvermogen

De Payload is een ander JSON-object dat het volgende bevat:

  • Publiek ("aud")
    • Dit is de oorsprong van de push-service ( NIET de oorsprong van uw site). In JavaScript kunt u het volgende doen om de doelgroep te achterhalen: const audience = new URL(subscription.endpoint).origin
  • Vervaltijd ("exp")
    • Dit is het aantal seconden voordat het verzoek als verlopen moet worden beschouwd. Dit MOET binnen 24 uur na indiening van het verzoek gebeuren, in UTC.
  • Onderwerp ("sub")
    • Het onderwerp moet een URL of een mailto: URL zijn. Dit biedt een aanspreekpunt voor het geval de pushdienst contact moet opnemen met de afzender van het bericht.

Een voorbeeld van een payload zou er als volgt uit kunnen zien:

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

Dit JSON-object is base64-URL-gecodeerd en vormt het tweede deel van de JWT.

Handtekening

De handtekening is het resultaat van het samenvoegen van de gecodeerde header en payload met een punt en vervolgens het coderen van het resultaat met behulp van de VAPID-privésleutel die u eerder hebt gemaakt. Het resultaat zelf moet met een punt aan de kop worden toegevoegd.

Ik ga hiervoor geen codevoorbeeld laten zien, omdat er een aantal bibliotheken zijn die de header- en payload-JSON-objecten gebruiken en deze handtekening voor u genereren.

De ondertekende JWT wordt gebruikt als de Authorization-header met 'WebPush' ervoor en ziet er ongeveer als volgt uit:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Let hierbij op een paar dingen. Ten eerste bevat de Authorization-header letterlijk het woord 'WebPush' en moet worden gevolgd door een spatie en vervolgens de JWT. Let ook op de punten die de JWT-header, payload en handtekening scheiden.

Crypto-Key-header

Naast de Authorization-header moet u uw openbare VAPID-sleutel toevoegen aan de Crypto-Key header als een base64 URL-gecodeerde string met p256ecdsa= ervoor.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

Wanneer u een melding verzendt met gecodeerde gegevens, gebruikt u al de Crypto-Key header. Om de applicatieserversleutel toe te voegen hoeft u dus alleen maar een puntkomma toe te voegen voordat u de bovenstaande inhoud toevoegt, wat resulteert in:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

Realiteit van deze veranderingen

Met VAPID hoeft u zich niet langer aan te melden voor een account bij GCM om push in Chrome te gebruiken en kunt u hetzelfde codepad gebruiken voor het abonneren van een gebruiker en het verzenden van een bericht naar een gebruiker in zowel Chrome als Firefox. Beiden volgen de normen.

Waar u rekening mee moet houden, is dat u in Chrome 51 en eerder, Opera voor Android en de Samsung-browser nog steeds de gcm_sender_id in uw web-app-manifest moet definiëren en dat u de Authorization-header moet toevoegen aan het FCM-eindpunt dat zal worden geretourneerd.

VAPID biedt een off-ramp van deze eigen vereisten. Als u VAPID implementeert, werkt het in alle browsers die webpush ondersteunen. Naarmate meer browsers VAPID ondersteunen, kunt u beslissen wanneer u de gcm_sender_id uit uw manifest verwijdert.