Vittorie interoperabilità web push

Matt Gaunt
Joe Medley
Joe Medley

Quando Chrome ha supportato per la prima volta l'API Web Push, si basava sul servizio push Firebase Cloud Messaging (FCM), precedentemente noto come Google Cloud Messaging (GCM). Per farlo, era necessario utilizzare la sua API proprietaria. In questo modo, Chrome ha potuto mettere a disposizione degli sviluppatori l'API Web Push quando le specifiche del protocollo Web Push erano ancora in fase di scrittura e in seguito ha fornito l'autenticazione (ovvero che il mittente del messaggio è chi dice di essere) quando il protocollo Web Push non la supportava. Buone notizie: non è più così.

FCM / GCM e Chrome ora supportano il Web Push Protocol standard, mentre l'autenticazione del mittente può essere ottenuta implementando VAPID, il che significa che la tua app web non ha più bisogno di un "gcm_sender_id".

In questo articolo, descriverò innanzitutto come convertire il codice del server esistente per utilizzare il protocollo Web Push con FCM. A questo punto, ti mostrerò come implementare VAPID sia nel codice client sia in quello del server.

FCM supporta il protocollo Web Push

Iniziamo con un po' di contesto. Quando la tua applicazione web si registra per un abbonamento push, riceve l'URL di un servizio push. Il tuo server utilizzerà questo endpoint per inviare dati all'utente tramite la tua app web. In Chrome ti verrà fornito un endpoint FCM se registri un utente senza VAPID. (parleremo di VAPID più avanti). Prima che FCM supportasse il protocollo Web Push, dovevi estrarre l'ID registrazione FCM dalla fine dell'URL e inserirlo nell'intestazione prima di effettuare una richiesta all'API FCM. Ad esempio, un endpoint FCM dihttps://android.googleapis.com/gcm/send/ABCD1234 avrà un ID registrazione di "ABCD1234".

Ora che FCM supporta Web Push Protocol, puoi lasciare l'endpoint invariato e utilizzare l'URL come endpoint Web Push Protocol. (In questo modo, sarà in linea con Firefox e, auspicabilmente, con tutti gli altri browser futuri).

Prima di esaminare VAPID, dobbiamo assicurarci che il codice del server gestisca correttamente l'endpoint FCM. Di seguito è riportato un esempio di invio di una richiesta a un servizio push in Node. Tieni presente che per FCM aggiungiamo la chiave API agli intestazioni della richiesta. Per altri endpoint di servizi push non sarà necessario. Per Chrome precedente alla versione 52, Opera Android e il browser Samsung, devi anche includere un valore "gcm_sender_id" nel file manifest.json della tua app web. La chiave API e l'ID mittente vengono utilizzati per verificare se il server che effettua le richieste è effettivamente autorizzato a inviare messaggi all'utente che li riceve.

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

Ricorda che si tratta di una modifica all'API FCM / GCM, quindi non è necessario aggiornare le iscrizioni, ma solo modificare il codice del server per definire le intestazioni come mostrato sopra.

Introduzione a VAPID per l'identificazione dei server

VAPID è il nuovo nome breve di "Voluntary Application Server Identification". Questa nuova specifica definisce essenzialmente un handshake tra il server dell'app e il servizio push e consente al servizio push di confermare quale sito invia i messaggi. Con VAPID puoi evitare i passaggi specifici di FCM per l'invio di un messaggio push. Non hai più bisogno di un progetto Firebase, di un'intestazione gcm_sender_id o Authorization.

La procedura è piuttosto semplice:

  1. Il server delle applicazioni crea una coppia di chiavi pubblica/privata. La chiave pubblica viene assegnata alla tua app web.
  2. Quando l'utente sceglie di ricevere push, aggiungi la chiave pubblica all'oggetto opzioni della chiamata subscribe().
  3. Quando il server di app invia un messaggio push, includi un token web JSON firmato insieme alla chiave pubblica.

Esaminiamo questi passaggi in dettaglio.

Creare una coppia di chiavi pubblica/privata

Non sono molto bravo con la crittografia, quindi ecco la sezione pertinente della specifica relativa al formato delle chiavi pubbliche/private VAPID:

I server delle applicazioni DEVONO generare e mantenere una coppia di chiavi di firma utilizzabile con la firma digitale a curva ellittica (ECDSA) sulla curva P-256.

Puoi scoprire come farlo nella libreria Node web-push:

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

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

Abbonamento con la chiave pubblica

Per abbonare un utente di Chrome per le notifiche push con la chiave pubblica VAPID, devi passare la chiave pubblica come Uint8Array utilizzando il parametro applicationServerKey del metodo subscribe().

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

Saprai se ha funzionato esaminando l'endpoint nell'oggetto subscription risultante. Se l'origine è fcm.googleapis.com, significa che funziona.

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

Invio di un messaggio push

Per inviare un messaggio utilizzando VAPID, devi effettuare una normale richiesta di protocollo push web con due intestazioni HTTP aggiuntive: un'intestazione Authorization e un'intestazione Crypto-Key.

Intestazione autorizzazione

L'intestazione Authorization è un JSON Web Token (JWT) firmato con la dicitura "WebPush " davanti.

Un JWT è un modo per condividere un oggetto JSON con una seconda parte in modo che la parte mittente possa firmarlo e la parte ricevente possa verificare che la firma provenga dal mittente previsto. La struttura di un JWT è composta da tre stringhe criptate, unite da un singolo punto.

<JWTHeader>.<Payload>.<Signature>

Intestazione JWT

L'intestazione JWT contiene il nome dell'algoritmo utilizzato per la firma e il tipo di token. Per VAPID, deve essere:

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

Questo viene poi codificato in base64 e forma la prima parte del JWT.

Payload

Il payload è un altro oggetto JSON contenente quanto segue:

  • Segmento di pubblico ("aud")
    • Si tratta dell'origine del servizio push (NON l'origine del tuo sito). In JavaScript, puoi eseguire le seguenti operazioni per ottenere il segmento di pubblico: const audience = new URL(subscription.endpoint).origin
  • Data/ora di scadenza ("exp")
    • Si tratta del numero di secondi prima che la richiesta debba essere considerata scaduta. DEVE essere inviata entro 24 ore dalla richiesta, in UTC.
  • Oggetto ("sub")
    • L'oggetto deve essere un URL o un URL mailto:. Questo fornisce un punto di contatto nel caso in cui il servizio push debba contattare il mittente del messaggio.

Un esempio di payload potrebbe essere il seguente:

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

Questo oggetto JSON è codificato in base64 e costituisce la seconda parte del JWT.

Firma

La firma è il risultato dell'unione dell'intestazione e del payload codificati con un punto, quindi della crittografia del risultato utilizzando la chiave privata VAPID creata in precedenza. Il risultato stesso deve essere aggiunto all'intestazione con un punto.

Non mostrerò un esempio di codice per questo, in quanto esistono diverse librerie che prendono gli oggetti JSON intestazione e payload e generano questa firma per te.

Il JWT firmato viene utilizzato come intestazione Authorization con "WebPush" premesso e avrà il seguente aspetto:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Tieni presente alcune cose. Innanzitutto, l'intestazione Authorization contiene la parola "WebPush" e deve essere seguita da uno spazio e dal JWT. Inoltre, tieni presente i punti che separano l'intestazione, il payload e la firma JWT.

Intestazione Crypto-Key

Oltre all'intestazione Authorization, devi aggiungere la chiave pubblica VAPID all'Crypto-Key intestazione come stringa con codifica URL base64 con p256ecdsa= premesso.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

Quando invii una notifica con dati criptati, utilizzerai già l'intestazione Crypto-Key, quindi per aggiungere la chiave del server di applicazioni devi solo aggiungere un punto e virgola prima di aggiungere i contenuti precedenti, ottenendo:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

Realtà di queste modifiche

Con VAPID non devi più registrarti a un account GCM per utilizzare la funzione push in Chrome e puoi utilizzare lo stesso percorso di codice per iscrivere un utente e inviare un messaggio a un utente sia in Chrome che in Firefox. Entrambi rispettano gli standard.

Tieni presente che in Chrome 51 e versioni precedenti, Opera per Android e nel browser Samsung dovrai comunque definire gcm_sender_id nel file manifest dell'app web e dovrai aggiungere l'intestazione Authorization all'endpoint FCM che verrà restituito.

VAPID offre un vantaggio rispetto a questi requisiti proprietari. Se implementi VAPID, la funzionalità funzionerà in tutti i browser che supportano le notifiche push web. Man mano che un numero maggiore di browser supporta VAPID, puoi decidere quando rimuovere gcm_sender_id dal manifest.