Protocole Web push

Nous avons vu comment une bibliothèque peut être utilisée pour déclencher des messages push, mais que font exactement ces bibliothèques ?

Ils effectuent des requêtes réseau tout en s'assurant que ces requêtes sont au bon format. La spécification qui définit cette requête réseau est le protocole Web Push.

Schéma de l'envoi d'un message push de votre serveur à un service push

Cette section explique comment le serveur peut s'identifier à l'aide de clés de serveur d'applications et comment la charge utile chiffrée et les données associées sont envoyées.

Ce n'est pas un aspect très attrayant du push Web, et je ne suis pas un expert du chiffrement, mais examinons chaque élément, car il est utile de savoir ce que font ces bibliothèques en arrière-plan.

Clés du serveur d'applications

Lorsque nous abonnons un utilisateur, nous transmettons un applicationServerKey. Cette clé est transmise au service de push et permet de vérifier que l'application qui a souscrit l'utilisateur est également celle qui déclenche les messages push.

Lorsque nous déclenchons un message push, nous envoyons un ensemble d'en-têtes qui permettent au service push d'authentifier l'application. (Ceci est défini par la spécification VAPID.)

Qu'est-ce que cela signifie concrètement et que se passe-t-il exactement ? Voici les étapes suivies pour l'authentification du serveur d'application :

  1. Le serveur d'applications signe des informations JSON avec sa clé d'application privée.
  2. Ces informations signées sont envoyées au service de push en tant qu'en-tête dans une requête POST.
  3. Le service de transfert par poussée utilise la clé publique stockée qu'il a reçue de pushManager.subscribe() pour vérifier que les informations reçues sont signées par la clé privée associée à la clé publique. Rappel: La clé publique est le applicationServerKey transmis dans l'appel d'abonnement.
  4. Si les informations signées sont valides, le service push envoie le message push à l'utilisateur.

Vous trouverez ci-dessous un exemple de ce flux d'informations. (Notez la légende en bas à gauche pour indiquer les clés publiques et privées.)

Illustration de l'utilisation de la clé privée du serveur d'application lors de l'envoi d'un message

Les "informations signées" ajoutées à un en-tête dans la requête sont un jeton Web JSON.

Jeton Web JSON

Un jeton Web JSON (ou JWT, pour "JSON Web Token") permet d'envoyer un message à un tiers afin que le destinataire puisse valider l'expéditeur.

Lorsqu'un tiers reçoit un message, il doit obtenir la clé publique de l'expéditeur et l'utiliser pour valider la signature du JWT. Si la signature est valide, le jeton JWT doit avoir été signé avec la clé privée correspondante et doit donc provenir de l'expéditeur attendu.

De nombreuses bibliothèques sur https://jwt.io/ peuvent effectuer la signature à votre place. Je vous recommande de le faire dans la mesure du possible. Pour être complet, voyons comment créer manuellement un jeton JWT signé.

Déploiement Web et jetons JWT signés

Un jeton JWT signé n'est qu'une chaîne, mais on peut le considérer comme trois chaînes jointes par des points.

Illustration des chaînes dans un jeton Web JSON

La première et la deuxième chaîne (les informations JWT et les données JWT) sont des éléments JSON encodés en base64, ce qui signifie qu'ils sont lisibles par tous.

La première chaîne contient des informations sur le jeton JWT lui-même, indiquant l'algorithme utilisé pour créer la signature.

Les informations JWT pour le push Web doivent contenir les informations suivantes :

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

La deuxième chaîne correspond aux données JWT. Elle fournit des informations sur l'expéditeur du jeton JWT, sur son destinataire et sur sa durée de validité.

Pour le transfert Web, les données doivent respecter le format suivant:

{
  "aud": "https://some-push-service.org",
  "exp": "1469618703",
  "sub": "mailto:example@web-push-book.org"
}

La valeur aud correspond à l'audience, c'est-à-dire à qui le jeton JWT est destiné. Pour le push Web, l'audience est le service push. Nous la définissons donc sur l'origine du service push.

La valeur exp correspond à l'expiration du jeton JWT. Cela empêche les pirates informatiques de pouvoir réutiliser un jeton JWT s'ils l'interceptent. L'expiration est un code temporel en secondes et ne doit pas dépasser 24 heures.

Dans Node.js, l'expiration est définie à l'aide de :

Math.floor(Date.now() / 1000) + 12 * 60 * 60;

Il faut 12 heures plutôt que 24 heures pour éviter tout problème de différence d'horloge entre l'application émettrice et le service push.

Enfin, la valeur sub doit être une URL ou une adresse e-mail mailto. Ainsi, si un service push doit contacter l'expéditeur, il peut trouver les informations de contact à partir du JWT. (C'est pourquoi la bibliothèque Web Push avait besoin d'une adresse e-mail).

Tout comme les informations JWT, les données JWT sont encodées en tant que chaîne base64 sécurisée pour les URL.

La troisième chaîne, la signature, est le résultat de la prise des deux premières chaînes (les informations JWT et les données JWT), de leur association avec un caractère point, que nous appellerons "jeton non signé", et de leur signature.

Le processus de signature nécessite le chiffrement du "jeton non signé" à l'aide d'ES256. Selon la spécification JWT, ES256 est l'abréviation d'"ECDSA à l'aide de la courbe P-256 et de l'algorithme de hachage SHA-256". À l'aide de la cryptographie Web, vous pouvez créer la signature comme suit :

// Utility function for UTF-8 encoding a string to an ArrayBuffer.
const utf8Encoder = new TextEncoder('utf-8');

// The unsigned token is the concatenation of the URL-safe base64 encoded
// header and body.
const unsignedToken = .....;

// Sign the |unsignedToken| using ES256 (SHA-256 over ECDSA).
const key = {
  kty: 'EC',
  crv: 'P-256',
  x: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(1, 33)),
  y: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(33, 65)),
  d: window.uint8ArrayToBase64Url(applicationServerKeys.privateKey),
};

// Sign the |unsignedToken| with the server's private key to generate
// the signature.
return crypto.subtle.importKey('jwk', key, {
  name: 'ECDSA', namedCurve: 'P-256',
}, true, ['sign'])
.then((key) => {
  return crypto.subtle.sign({
    name: 'ECDSA',
    hash: {
      name: 'SHA-256',
    },
  }, key, utf8Encoder.encode(unsignedToken));
})
.then((signature) => {
  console.log('Signature: ', signature);
});

Un service push peut valider un jeton JWT à l'aide de la clé publique du serveur d'applications pour déchiffrer la signature et s'assurer que la chaîne déchiffrée est identique au "jeton non signé" (c'est-à-dire aux deux premières chaînes du jeton JWT).

Le jeton JWT signé (c'est-à-dire les trois chaînes reliées par des points) est envoyé au service Web push en tant qu'en-tête Authorization avec le préfixe WebPush, comme suit:

Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]';

Le protocole Web Push indique également que la clé publique du serveur d'application doit être envoyée dans l'en-tête Crypto-Key sous la forme d'une chaîne encodée en base64 et sécurisée pour les URL, avec p256ecdsa= ajouté au début.

Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]

Chiffrement de la charge utile

Voyons maintenant comment envoyer une charge utile avec un message push afin que, lorsque notre application Web reçoit un message push, elle puisse accéder aux données qu'elle reçoit.

Une question courante qui se pose à tous ceux qui ont utilisé d'autres services push est la suivante : pourquoi la charge utile de push Web doit-elle être chiffrée ? Avec les applications natives, les messages push peuvent envoyer des données en texte brut.

L'un des avantages des notifications push Web est que, comme tous les services de notification push utilisent la même API (le protocole de notification push Web), les développeurs n'ont pas à se soucier de l'identité du service de notification push. Nous pouvons envoyer une requête au bon format et nous attendre à ce qu'un message de notification push soit envoyé. L'inconvénient est que les développeurs pourraient envoyer des messages à un service push non fiable. En chiffrant la charge utile, un service push ne peut pas lire les données envoyées. Seul le navigateur peut déchiffrer les informations. Cela protège les données de l'utilisateur.

Le chiffrement de la charge utile est défini dans la spécification de chiffrement des messages.

Avant d'examiner les étapes spécifiques pour chiffrer la charge utile d'un message push, nous devons aborder certaines techniques qui seront utilisées lors du processus de chiffrement. (Un grand merci à Mat Scales pour son excellent article sur le chiffrement par poussée.)

ECDH et HKDF

Les formats ECDH et HKDF sont tous deux utilisés tout au long du processus de chiffrement et présentent des avantages pour le chiffrement des informations.

ECDH : échange de clés Diffie-Hellman basé sur les courbes elliptiques

Imaginons que deux personnes, Alice et Bob, souhaitent partager des informations. Alice et Bob ont chacun leur propre clé publique et privée. Alice et Bob partagent leurs clés publiques.

La propriété utile des clés générées avec l'ECDH est qu'Alice peut utiliser sa clé privée et la clé publique de Bob pour créer la valeur secrète "X". Bob peut faire de même, en utilisant sa clé privée et la clé publique d'Alice pour créer indépendamment la même valeur "X". Cela fait de « X » un secret partagé et Alice et Bob n'ont besoin de partager que leur clé publique. Bob et Alice peuvent désormais utiliser "X" pour chiffrer et déchiffrer les messages qu'ils s'envoient.

À ma connaissance, l'ECDH définit les propriétés des courbes qui permettent cette "fonctionnalité" de créer une clé secrète partagée "X".

Ceci est une explication générale de l'ECDH. Si vous souhaitez en savoir plus, je vous invite à regarder cette vidéo.

En termes de code, la plupart des langages/plates-formes sont fournis avec des bibliothèques permettant de générer facilement ces clés.

Dans le nœud, procédez comme suit :

const keyCurve = crypto.createECDH('prime256v1');
keyCurve.generateKeys();

const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();

HKDF : fonction de dérivation de clé basée sur HMAC

Wikipedia propose une description succincte de HKDF :

HKDF est une fonction de dérivation de clé basée sur HMAC qui transforme tout matériel de clé faible en matériel de clé cryptographiquement fort. Il peut être utilisé, par exemple, pour convertir des secrets partagés échangés par Diffie-Hellman en matériel de clé adapté au chiffrement, à la vérification de l'intégrité ou à l'authentification.

En substance, HKDF prend une entrée qui n'est pas particulièrement sécurisée et la rend plus sécurisée.

La spécification qui définit ce chiffrement nécessite l'utilisation de SHA-256 comme algorithme de hachage, et les clés résultantes pour HKDF dans le push Web ne doivent pas dépasser 256 bits (32 octets).

Dans le nœud, cela peut être implémenté comme suit:

// Simplified HKDF, returning keys up to 32 bytes long
function hkdf(salt, ikm, info, length) {
  // Extract
  const keyHmac = crypto.createHmac('sha256', salt);
  keyHmac.update(ikm);
  const key = keyHmac.digest();

  // Expand
  const infoHmac = crypto.createHmac('sha256', key);
  infoHmac.update(info);

  // A one byte long buffer containing only 0x01
  const ONE_BUFFER = new Buffer(1).fill(1);
  infoHmac.update(ONE_BUFFER);

  return infoHmac.digest().slice(0, length);
}

Cet exemple de code est inspiré de l'article de Mat Scale.

Cela couvre de manière approximative ECDH et HKDF.

ECDH est un moyen sécurisé de partager des clés publiques et de générer un secret partagé. HKDF permet de sécuriser des éléments non sécurisés.

Il sera utilisé lors du chiffrement de notre charge utile. Voyons ensuite ce que nous considérons comme entrée et comment elle est chiffrée.

Entrées

Lorsque nous souhaitons envoyer un message push à un utilisateur avec une charge utile, trois entrées sont nécessaires :

  1. La charge utile elle-même.
  2. Le secret auth de PushSubscription.
  3. Clé p256dh de PushSubscription.

Nous avons constaté que les valeurs auth et p256dh étaient récupérées à partir d'un PushSubscription. Toutefois, pour rappel, pour un abonnement, nous aurions besoin des valeurs suivantes:

subscription.toJSON().keys.auth;
subscription.toJSON().keys.p256dh;

subscription.getKey('auth');
subscription.getKey('p256dh');

La valeur auth doit être traitée comme un secret et ne doit pas être partagée en dehors de votre application.

La clé p256dh est une clé publique, parfois appelée clé publique du client. Ici, nous désignerons p256dh comme clé publique d'abonnement. La clé publique d'abonnement est générée par le navigateur. Le navigateur conserve la clé privée secrète et l'utilise pour déchiffrer la charge utile.

Ces trois valeurs, auth, p256dh et payload, sont nécessaires en entrée. Le résultat du processus de chiffrement sera la charge utile chiffrée, une valeur de sel et une clé publique utilisée uniquement pour chiffrer les données.

Sel

Le sel doit être composé de 16 octets de données aléatoires. Dans NodeJS, procédez comme suit pour créer un sel :

const salt = crypto.randomBytes(16);

Clés publiques/privées

Les clés publiques et privées doivent être générées à l'aide d'une courbe elliptique P-256, ce que nous ferons dans Node comme suit :

const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve.generateKeys();

const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();

Ces clés sont appelées "clés locales". Elles sont utilisées uniquement pour le chiffrement et n'ont rien à voir avec les clés du serveur d'applications.

Avec la charge utile, le secret d'authentification et la clé publique d'abonnement comme entrées, ainsi qu'un sel et un ensemble de clés locales nouvellement générés, nous sommes prêts à effectuer un chiffrement.

Secret partagé

La première étape consiste à créer un secret partagé à l'aide de la clé publique de l'abonnement et de notre nouvelle clé privée (rappelez-vous l'explication de l'ECDH avec Alice et Bob ? Tout simplement.)

const sharedSecret = localKeysCurve.computeSecret(
  subscription.keys.p256dh,
  'base64',
);

Elle sera utilisée à l'étape suivante pour calculer la clé pseudo-aléatoire (PRK).

Clé pseudo-aléatoire

La clé pseudo-aléatoire (PRK) est la combinaison du secret d'authentification de l'abonnement push et du secret partagé que nous venons de créer.

const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8');
const prk = hkdf(subscription.keys.auth, sharedSecret, authEncBuff, 32);

Vous vous demandez peut-être à quoi sert la chaîne Content-Encoding: auth\0. En résumé, il n'a pas d'objectif clair, bien que les navigateurs puissent déchiffrer un message entrant et rechercher l'encodage de contenu attendu. \0 ajoute un octet avec une valeur de 0 à la fin du tampon. Les navigateurs qui déchiffrent le message s'attendent à autant d'octets pour l'encodage du contenu, suivis d'un octet avec la valeur 0, puis des données chiffrées.

Notre clé pseudo-aléatoire exécute simplement l'authentification, le secret partagé et une partie des informations d'encodage via HKDF (c'est-à-dire en la renforçant cryptographiquement).

Contexte

Le "contexte" est un ensemble d'octets utilisé pour calculer deux valeurs plus tard dans le navigateur de chiffrement. Il s'agit essentiellement d'un tableau d'octets contenant la clé publique d'abonnement et la clé publique locale.

const keyLabel = new Buffer('P-256\0', 'utf8');

// Convert subscription public key into a buffer.
const subscriptionPubKey = new Buffer(subscription.keys.p256dh, 'base64');

const subscriptionPubKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = subscriptionPubKey.length;

const localPublicKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = localPublicKey.length;

const contextBuffer = Buffer.concat([
  keyLabel,
  subscriptionPubKeyLength.buffer,
  subscriptionPubKey,
  localPublicKeyLength.buffer,
  localPublicKey,
]);

Le tampon de contexte final est un libellé, le nombre d'octets de la clé publique d'abonnement, suivi de la clé elle-même, puis du nombre d'octets de la clé publique locale, suivi de la clé elle-même.

Cette valeur de contexte nous permet de l'utiliser dans la création d'un nonce et d'une clé de chiffrement de contenu (CEK).

Clé de chiffrement de contenu et nonce

Un nonce est une valeur qui empêche les attaques par rejeu, car elle ne doit être utilisée qu'une seule fois.

La clé de chiffrement du contenu (CEK) est la clé qui sera finalement utilisée pour chiffrer notre charge utile.

Nous devons d'abord créer les octets de données pour le nonce et le CEK, qui est simplement une chaîne d'encodage de contenu suivie du tampon de contexte que nous venons de calculer :

const nonceEncBuffer = new Buffer('Content-Encoding: nonce\0', 'utf8');
const nonceInfo = Buffer.concat([nonceEncBuffer, contextBuffer]);

const cekEncBuffer = new Buffer('Content-Encoding: aesgcm\0');
const cekInfo = Buffer.concat([cekEncBuffer, contextBuffer]);

Ces informations sont exécutées via HKDF en combinant le sel et la PRK avec les nonceInfo et cekInfo :

// The nonce should be 12 bytes long
const nonce = hkdf(salt, prk, nonceInfo, 12);

// The CEK should be 16 bytes long
const contentEncryptionKey = hkdf(salt, prk, cekInfo, 16);

Nous obtenons ainsi notre nonce et notre clé de chiffrement de contenu.

Effectuer le chiffrement

Maintenant que nous disposons de notre clé de chiffrement du contenu, nous pouvons chiffrer la charge utile.

Nous créons un algorithme de chiffrement AES128 à l'aide de la clé de chiffrement du contenu comme clé, et le nonce est un vecteur d'initialisation.

Dans Node, procédez comme suit :

const cipher = crypto.createCipheriv(
  'id-aes128-GCM',
  contentEncryptionKey,
  nonce,
);

Avant de chiffrer notre charge utile, nous devons définir la marge intérieure que nous souhaitons ajouter à l'avant de la charge utile. La raison pour laquelle nous souhaitons ajouter un remplissage est qu'il empêche les écoutes clandestines de pouvoir déterminer les "types" de messages en fonction de la taille de la charge utile.

Vous devez ajouter deux octets de marge intérieure pour indiquer la longueur de toute marge intérieure supplémentaire.

Par exemple, si vous n'avez ajouté aucun remplissage, vous disposez de deux octets avec la valeur 0, c'est-à-dire qu'aucun remplissage n'existe. Après ces deux octets, vous lirez la charge utile. Si vous avez ajouté cinq octets de remplissage, les deux premiers octets auront une valeur de cinq. Le consommateur lira donc cinq octets supplémentaires, puis commencera à lire la charge utile.

const padding = new Buffer(2 + paddingLength);
// The buffer must be only zeros, except the length
padding.fill(0);
padding.writeUInt16BE(paddingLength, 0);

Nous exécutons ensuite notre remplissage et notre charge utile via ce chiffrement.

const result = cipher.update(Buffer.concat(padding, payload));
cipher.final();

// Append the auth tag to the result -
// https://nodejs.org/api/crypto.html#crypto_cipher_getauthtag
const encryptedPayload = Buffer.concat([result, cipher.getAuthTag()]);

Nous avons maintenant notre charge utile chiffrée. Super !

Il ne reste plus qu'à déterminer comment cette charge utile est envoyée au service push.

En-têtes et corps de la charge utile chiffrée

Pour envoyer cette charge utile chiffrée au service de transfert, nous devons définir quelques en-têtes différents dans notre requête POST.

En-tête de chiffrement

L'en-tête "Encryption" (Chiffrement) doit contenir le sel utilisé pour chiffrer la charge utile.

La valeur salt de 16 octets doit être encodée au format URL en base64 et ajoutée à l'en-tête de chiffrement, comme suit:

Encryption: salt=[URL Safe Base64 Encoded Salt]

En-tête de clé cryptographique

Nous avons vu que l'en-tête Crypto-Key est utilisé dans la section "Clés du serveur d'applications" pour contenir la clé publique du serveur d'applications.

Cet en-tête permet également de partager la clé publique locale servant à chiffrer la charge utile.

L'en-tête obtenu se présente comme suit :

Crypto-Key: dh=[URL Safe Base64 Encoded Local Public Key String]; p256ecdsa=[URL Safe Base64 Encoded Public Application Server Key]

En-têtes de type, longueur et encodage de contenu

L'en-tête Content-Length correspond au nombre d'octets de la charge utile chiffrée. Les en-têtes "Content-Type" et "Content-Encoding" sont des valeurs fixes. Ce processus est illustré ci-dessous.

Content-Length: [Number of Bytes in Encrypted Payload]
Content-Type: 'application/octet-stream'
Content-Encoding: 'aesgcm'

Avec ces en-têtes définis, nous devons envoyer la charge utile chiffrée en tant que corps de la requête. Notez que Content-Type est défini sur application/octet-stream. En effet, la charge utile chiffrée doit être envoyée sous forme de flux d'octets.

Dans NodeJS, procédez comme suit :

const pushRequest = https.request(httpsOptions, function(pushResponse) {
pushRequest.write(encryptedPayload);
pushRequest.end();

D'autres en-têtes ?

Nous avons abordé les en-têtes utilisés pour les clés JWT/Application Server (c'est-à-dire comment identifier l'application avec le service push) et les en-têtes utilisés pour envoyer une charge utile chiffrée.

Les services push utilisent des en-têtes supplémentaires pour modifier le comportement des messages envoyés. Certains de ces en-têtes sont obligatoires, d'autres sont facultatifs.

En-tête TTL

Obligatoire

TTL (ou valeur TTL) est un entier spécifiant le nombre de secondes pendant lesquelles vous souhaitez que votre message push reste actif sur le service push avant sa distribution. Lorsque l'TTL expire, le message est supprimé de la file d'attente du service de transfert et n'est pas distribué.

TTL: [Time to live in seconds]

Si vous définissez TTL sur zéro, le service de transfert de messages tentera d'envoyer le message immédiatement, mais si l'appareil ne peut pas être joint, votre message sera immédiatement supprimé de la file d'attente du service de transfert de messages.

Techniquement, un service push peut réduire la TTL d'un message push s'il le souhaite. Pour savoir si cela s'est produit, examinez l'en-tête TTL dans la réponse d'un service push.

Thème

Optional

Les sujets sont des chaînes qui peuvent être utilisées pour remplacer un message en attente par un nouveau message s'il possède des noms de sujet correspondants.

Cela est utile lorsque plusieurs messages sont envoyés alors qu'un appareil est hors connexion et que vous ne souhaitez vraiment qu'un utilisateur ne voie que le dernier message lorsque l'appareil est allumé.

Urgence

Optional

L'urgence indique au service de notification push l'importance d'un message pour l'utilisateur. Le service push peut l'utiliser pour préserver l'autonomie de la batterie de l'appareil d'un utilisateur en ne l'activant pour les messages importants que lorsque la batterie est faible.

La valeur de l'en-tête est définie comme indiqué ci-dessous. La valeur par défaut est normal.

Urgency: [very-low | low | normal | high]

Tout ensemble

Si vous avez d'autres questions sur le fonctionnement de tout cela, vous pouvez toujours voir comment les bibliothèques déclenchent des messages push sur l'organisation web-push-libs.

Une fois que vous disposez d'une charge utile chiffrée et des en-têtes ci-dessus, il vous suffit d'envoyer une requête POST à endpoint dans un PushSubscription.

Que faire de la réponse à cette requête POST ?

Réponse du service de transfert

Une fois que vous avez envoyé une requête à un service push, vous devez vérifier le code d'état de la réponse, car il vous indiquera si la requête a abouti ou non.

Code d'état Description
201 Création terminée La demande d'envoi d'un message push a été reçue et acceptée.
429 Trop de requêtes. Cela signifie que votre serveur d'application a atteint une limite de débit avec un service push. Le service de transfert de données doit inclure un en-tête "Retry-After" pour indiquer le délai avant qu'une autre requête puisse être envoyée.
400 Demande incorrecte. Cela signifie généralement que l'un de vos en-têtes n'est pas valide ou que son format est incorrect.
404 Introuvable. Cela indique que l'abonnement a expiré et qu'il ne peut plus être utilisé. Dans ce cas, vous devez supprimer "PushSubscription" et attendre que le client réabonne l'utilisateur.
410 Supprimé. L'abonnement n'est plus valide et doit être supprimé du serveur d'application. Pour reproduire ce problème, appelez "unsubscribe()" sur un "PushSubscription".
413 Taille de la charge utile trop importante. La taille minimale de la charge utile qu'un service push doit prendre en charge est de 4 096 octets (soit 4 Ko).

Pour en savoir plus sur les codes d'état HTTP, vous pouvez également consulter la norme Web Push (RFC8030).

Étapes suivantes

Ateliers de programmation