Vittorie interoperabilità web push

Matt Gaunt
Joe Medley
Mario Bianchi

Quando Chrome supportava per la prima volta l'API Web Push, si basava sul servizio push di Firebase Cloud Messaging (FCM), precedentemente noto come Google Cloud Messaging (GCM). Ciò richiedeva l'uso della sua API proprietaria. In questo modo Chrome ha reso disponibile l'API Web Push agli sviluppatori in un momento in cui la specifica del Web Push Protocol era ancora in fase di scrittura e in seguito ha fornito l'autenticazione (il che significa che il mittente del messaggio è chi dice di essere) in un momento in cui mancava il protocollo Web Push Protocol. Buone notizie: nessuna delle due affermazioni è vera.

FCM / GCM e Chrome supportano ora il Web Push Protocol standard, mentre l'autenticazione del mittente può essere ottenuta implementando VAPID, il che significa che l'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. Ora ti mostrerò come implementare VAPID nel codice client e server.

FCM supporta il protocollo Web Push

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

Ora che FCM supporta il Web Push Protocol, puoi lasciare l'endpoint invariato e utilizzare l'URL come endpoint Web Push Protocol. (Questo lo allinea con Firefox e, speriamo, con tutti gli altri browser in futuro.)

Prima di addentrarci in VAPID, dobbiamo assicurarci che il nostro codice del server gestisca correttamente l'endpoint FCM. Di seguito è riportato un esempio di richiesta a un servizio push in Node. Nota che per FCM stiamo aggiungendo la chiave API alle intestazioni delle richieste. Non sarà necessario per gli altri endpoint del servizio push. Per Chrome precedenti alla versione 52, Opera Android e il browser Samsung, devi comunque includere "gcm_sender_id" nel file manifest.json dell'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 ricevente.

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 di FCM / GCM, pertanto non è necessario aggiornare gli abbonamenti, ma è sufficiente 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 per "Voluntary Application Server Identification". Questa nuova specifica definisce essenzialmente un handshake tra il server delle app e il servizio push e consente al servizio push di confermare quale sito invia messaggi. Con VAPID puoi evitare i passaggi specifici di FCM per l'invio di un messaggio push. Non è più necessario un progetto Firebase, 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 fornita alla tua app web.
  2. Quando l'utente sceglie di ricevere i push, aggiungi la chiave pubblica all'oggetto opzioni della chiamata subscribe().
  3. Quando il server delle app invia un messaggio push, includi un token web JSON firmato insieme alla chiave pubblica.

Diamo un'occhiata a questi passaggi in dettaglio.

Crea una coppia di chiavi pubblica/privata

Non sono a conoscenza del problema con la crittografia, quindi ecco la sezione pertinente delle specifiche relative 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 vedere come eseguire questa operazione nella libreria dei nodi web-push:

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

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

Iscrizione con la chiave pubblica

Per sottoscrivere un utente di Chrome per il 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 di sottoscrizione risultante. Se l'origine è fcm.googleapis.com, 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 Web push con due intestazioni HTTP aggiuntive: un'intestazione Authorization e un'intestazione Crypto-Key.

Intestazione autorizzazione

L'intestazione Authorization è un token web JSON (JWT) firmato con davanti la scritta "WebPush".

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 è costituita da tre stringhe crittografate, unite da un singolo punto tra loro.

<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 quindi codificato con URL in Base64 e costituisce la prima parte del JWT.

Payload

Il payload è un altro oggetto JSON che contiene quanto segue:

  • Pubblico ("aud")
    • Questa è l'origine del servizio push (NON l'origine del tuo sito). In JavaScript, puoi ottenere il segmento di pubblico procedendo nel seguente modo: const audience = new URL(subscription.endpoint).origin
  • Data di scadenza ("exp")
    • Questo è il numero di secondi prima che la richiesta venga considerata scaduta. DEVE essere entro 24 ore dall'invio della richiesta, nel fuso orario 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 payload di esempio potrebbe avere il seguente aspetto:

{
    "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 ha un URL con codifica Base64 e costituisce la seconda parte del JWT.

Firma

La firma è il risultato dell'unione dell'intestazione codificata e del payload con un punto, quindi alla crittografia del risultato utilizzando la chiave privata VAPID che hai creato in precedenza. Il risultato dovrebbe essere aggiunto all'intestazione con un punto.

Non ho intenzione di mostrare un campione di codice per questo perché esistono numerose librerie che acquisiscono gli oggetti JSON di intestazione e payload e generano questa firma.

Il JWT firmato viene utilizzato come intestazione Authorization con l'aggiunta di "WebPush" e sarà simile al seguente:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Ecco alcune informazioni in merito. Innanzitutto, l'intestazione Authorization contiene letteralmente la parola "WebPush" e deve essere seguita da uno spazio e poi da JWT. Nota anche i punti che separano l'intestazione, il payload e la firma JWT.

Intestazione Crypto-Key

Oltre all'intestazione Authorization, devi aggiungere la tua chiave pubblica VAPID all'intestazione Crypto-Key come stringa codificata URL Base64 con antecedente p256ecdsa=.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

Quando invii una notifica con dati criptati, utilizzerai già l'intestazione Crypto-Key, quindi per aggiungere la chiave server applicazioni è sufficiente aggiungere un punto e virgola prima di aggiungere i contenuti sopra riportati, ottenendo il seguente risultato:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

La realtà di questi cambiamenti

Con VAPID non è più necessario creare un account con GCM per utilizzare il 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 seguono gli standard.

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

VAPID fornisce una rampa di abbandono per questi requisiti proprietari. Se implementi VAPID, funzionerà in tutti i browser che supportano il web push. Poiché sempre più browser supportano VAPID, puoi decidere quando eliminare gcm_sender_id dal file manifest.