L'interopérabilité Web push l'emporte

Joe Medley
Joe Medley

Lorsque Chrome a commencé à prendre en charge l'API Web Push, il s'est appuyé sur Firebase Cloud Messaging (FCM),anciennement connu sous le nom de Google Cloud Messaging (GCM), service push. Pour cela, il fallait utiliser son API propriétaire. Cela a permis à Chrome de mettre l'API Web Push à la disposition des développeurs à un moment où la spécification du protocole Web Push était encore en cours d'écriture et qui fournissait ensuite une authentification (c'est-à-dire que l'expéditeur du message était bien celui qu'il prétend être) alors que le protocole Web Push n'en était pas assez. Bonne nouvelle: aucune de ces affirmations n'est vraie.

FCM / GCM et Chrome sont désormais compatibles avec le protocole push Web standard, tandis que l'authentification de l'expéditeur peut être obtenue en implémentant VAPID, ce qui signifie que votre application Web n'a plus besoin de "gcm_sender_id".

Dans cet article, nous allons d'abord décrire comment convertir votre code serveur existant pour utiliser le protocole Web Push avec FCM. Ensuite, je vais vous montrer comment implémenter VAPID dans le code client et le code serveur.

FCM est compatible avec le protocole Web Push

Commençons par un peu de contexte. Lorsque votre application Web s'inscrit pour un abonnement push, elle reçoit l'URL d'un service push. Votre serveur utilisera ce point de terminaison pour envoyer des données à votre utilisateur via votre application Web. Dans Chrome, vous recevrez un point de terminaison FCM si vous abonnez un utilisateur sans VAPID. (Nous aborderons VAPID plus tard). Avant que FCM ne prenne en charge le protocole Web Push, vous deviez extraire l'ID d'enregistrement FCM à la fin de l'URL et le placer dans l'en-tête avant d'effectuer une requête API FCM. Par exemple, un point de terminaison FCM de https://android.googleapis.com/gcm/send/ABCD1234 aurait l'ID d'enregistrement "ABCD1234".

Maintenant que FCM est compatible avec le protocole Web push, vous pouvez laisser le point de terminaison intact et utiliser l'URL comme point de terminaison du protocole push Web. (Cela l'harmonise avec Firefox et, espérons-le, tous les autres navigateurs à l'avenir.)

Avant de nous plonger dans le concept VAPID, nous devons nous assurer que le code de notre serveur gère correctement le point de terminaison FCM. Vous trouverez ci-dessous un exemple d'envoi d'une requête à un service push dans Node. Notez que pour FCM, nous ajoutons la clé API aux en-têtes de requête. Cela n'est pas nécessaire pour les autres points de terminaison de service push. Pour Chrome antérieurs à la version 52, avec Opera pour Android et le navigateur Samsung, vous devez également inclure "gcm_sender_id" dans le fichier manifest.json de votre application Web. La clé API et l'ID de l'expéditeur permettent de vérifier si le serveur effectuant les requêtes est réellement autorisé à envoyer des messages à l'utilisateur destinataire.

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

N'oubliez pas qu'il s'agit d'une modification de l'API de FCM / GCM. Vous n'avez donc pas besoin de mettre à jour vos abonnements. Il vous suffit de modifier le code de votre serveur pour définir les en-têtes, comme indiqué ci-dessus.

Présentation de VAPID pour l'identification des serveurs

VAPID est le nouveau nom court "Voluntary Application Server Identification" (Identification du serveur d'applications volontaire). Cette nouvelle spécification définit essentiellement un handshake entre votre serveur d'applications et le service push, et permet au service push de confirmer le site qui envoie des messages. Avec VAPID, vous pouvez éviter les étapes spécifiques à FCM pour l'envoi d'un message push. Vous n'avez plus besoin d'un projet Firebase, d'un gcm_sender_id ni d'un en-tête Authorization.

Le processus est assez simple:

  1. Votre serveur d'applications crée une paire de clés publique/privée. La clé publique est fournie à votre application Web.
  2. Lorsque l'utilisateur choisit de recevoir des notifications push, ajoutez la clé publique à l'objet d'options de l'appel Subscribe().
  3. Lorsque votre serveur d'applications envoie un message push, incluez un jeton Web JSON signé avec la clé publique.

Examinons ces étapes en détail.

Créer une paire de clés publique/privée

Mon problème en termes de chiffrement est gênant. Voici donc la section correspondante de la spécification concernant le format des clés publiques/privées VAPID:

Les serveurs d'applications DOIVENT générer et gérer une paire de clés de signature utilisable avec une signature numérique à courbe elliptique (ECDSA) sur la courbe P-256.

Pour savoir comment procéder, consultez la bibliothèque de nœuds web-push:

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

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

S'abonner avec la clé publique

Pour abonner un utilisateur Chrome pour le mode push avec la clé publique VAPID, vous devez transmettre la clé publique en tant que Uint8Array à l'aide du paramètre applicationServerKey de la méthode subscription().

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

Pour savoir si le processus a fonctionné, examinez le point de terminaison dans l'objet d'abonnement obtenu. Si l'origine est fcm.googleapis.com, cela signifie qu'il fonctionne.

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

Envoyer un message push

Pour envoyer un message à l'aide de VAPID, vous devez effectuer une requête Web Push Protocol normale avec deux en-têtes HTTP supplémentaires: un en-tête Authorization et un en-tête Crypto-Key.

En-tête d'autorisation

L'en-tête Authorization est un jeton Web JSON (JWT) signé portant le préfixe "WebPush".

Un jeton JWT est un moyen de partager un objet JSON avec une seconde partie de sorte que la partie expéditeur puisse le signer et que la partie destinataire puisse vérifier que la signature provient de l'expéditeur attendu. La structure d'un jeton JWT se compose de trois chaînes chiffrées, reliées par un point unique.

<JWTHeader>.<Payload>.<Signature>

En-tête JWT

L'en-tête JWT contient le nom de l'algorithme utilisé pour la signature et le type de jeton. Pour VAPID, il doit s'agir:

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

Elle est ensuite encodée au format URL en base64 et constitue la première partie du jeton JWT.

Charge utile

La charge utile est un autre objet JSON contenant les éléments suivants:

  • Audience ("aud")
    • Il s'agit de l'origine du service push (et NON de l'origine de votre site). Dans JavaScript, vous pouvez effectuer les opérations suivantes pour obtenir l'audience: const audience = new URL(subscription.endpoint).origin
  • Date et heure d'expiration ("exp")
    • Il s'agit du nombre de secondes restant avant que la requête soit considérée comme expirée. Cette opération DOIT être effectuée dans les 24 heures suivant l'envoi de la requête, au format UTC.
  • Objet ("sous")
    • L'objet doit être une URL ou une URL mailto:. Cela fournit un point de contact au cas où le service push aurait besoin de contacter l'expéditeur du message.

Voici un exemple de charge utile:

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

Cet objet JSON est encodé au format URL en base64 et constitue la deuxième partie du jeton JWT.

Signature

La signature est le résultat de la jointure de l'en-tête et de la charge utile encodés à un point, puis du chiffrement du résultat à l'aide de la clé privée VAPID que vous avez créée précédemment. Le résultat lui-même doit être ajouté à l'en-tête avec un point.

Je ne vais pas vous montrer d'exemple de code, car de nombreuses bibliothèques vont prendre l'en-tête et les objets JSON de charge utile et générer cette signature pour vous.

Le jeton JWT signé est utilisé comme en-tête d'autorisation avec le préfixe "WebPush" et se présente comme suit:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Notez quelques points à ce sujet. Tout d'abord, l'en-tête Authorization contient littéralement le mot "WebPush" et doit être suivi d'un espace, puis du JWT. Notez également la présence de points séparant l'en-tête JWT, la charge utile et la signature.

En-tête de clé cryptographique

En plus de l'en-tête "Authorization", vous devez ajouter votre clé publique VAPID à l'en-tête Crypto-Key en tant que chaîne encodée au format URL base64 avec le préfixe p256ecdsa=.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

Lorsque vous envoyez une notification contenant des données chiffrées, vous utilisez déjà l'en-tête Crypto-Key. Par conséquent, pour ajouter la clé du serveur d'application, il vous suffit d'ajouter un point-virgule avant d'ajouter le contenu ci-dessus, ce qui entraîne:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

La réalité de ces changements

Avec VAPID, vous n'avez plus besoin de créer un compte avec GCM pour utiliser la fonctionnalité Push dans Chrome. Vous pouvez également utiliser le même chemin de code pour abonner un utilisateur et lui envoyer un message dans Chrome et Firefox. Les deux respectent les normes.

N'oubliez pas que dans Chrome 51 et les versions antérieures, avec Opera pour Android et le navigateur Samsung, vous devrez toujours définir gcm_sender_id dans le fichier manifeste de votre application Web et ajouter l'en-tête "Authorization" au point de terminaison FCM qui sera renvoyé.

Le VAPID permet de s'écarter de ces exigences propriétaires. La mise en œuvre de la fonction VAPID fonctionne dans tous les navigateurs compatibles avec la fonctionnalité Web push. Étant donné que de plus en plus de navigateurs sont compatibles avec VAPID, vous pouvez décider quand supprimer gcm_sender_id de votre fichier manifeste.