Melhorias na interoperabilidade por push na Web

Joe medley
Joe Medley

Quando o Chrome era compatível com a API Web Push, ele usava o Firebase Cloud Messaging (FCM),anteriormente conhecido como Google Cloud Messaging (GCM), o serviço de push. Isso exigia o uso de sua API reservada. Isso permitiu que o Chrome disponibiliza a API Web Push para desenvolvedores quando a especificação do Web Push Protocol ainda estava sendo escrita e depois fornecia autenticação (ou seja, o remetente da mensagem é quem diz ser) quando o Web Push Protocol não tinha isso. Boas notícias: nenhuma dessas situações é mais verdadeira.

O FCM / GCM e o Chrome agora oferecem suporte ao protocolo push da Web padrão, enquanto a autenticação do remetente pode ser feita implementando VAPID, o que significa que seu app da Web não precisa mais de um "gcm_sender_id".

Neste artigo, primeiro vou descrever como converter o código de servidor existente para usar o Web Push Protocol com o FCM. Em seguida, mostrarei como implementar o VAPID no código de cliente e servidor.

O FCM oferece suporte ao protocolo de push da Web

Vamos começar com um contexto. Quando seu aplicativo da Web se registra em uma assinatura de push, ele recebe o URL de um serviço de push. Seu servidor usará esse endpoint para enviar dados ao usuário por meio do app da Web. No Chrome, você receberá um endpoint do FCM se inscrever um usuário sem VAPID. Falaremos sobre VAPID mais tarde. Antes de o FCM aceitar o protocolo de push da Web, era preciso extrair o código de registro do FCM do final do URL e colocá-lo no cabeçalho antes de fazer uma solicitação à API FCM. Por exemplo, um endpoint do FCM de https://android.googleapis.com/gcm/send/ABCD1234 teria o ID de registro "ABCD1234".

Agora que o FCM é compatível com o Web Push Protocol, deixe o endpoint intacto e use o URL como um endpoint do Web Push Protocol. (Isso o alinha com o Firefox e esperamos que todos os outros navegadores futuros.)

Antes de nos aprofundarmos na VAPID, precisamos garantir que nosso código do servidor processe corretamente o endpoint do FCM. Abaixo está um exemplo de como fazer uma solicitação para um serviço de push no Node. Para o FCM, estamos adicionando a chave de API aos cabeçalhos das solicitações. Isso não será necessário para outros endpoints de serviços de push. No Chrome anterior à versão 52, no Opera Android e no navegador Samsung, ainda é necessário incluir um "gcm_sender_id" no manifesto.json do seu app da Web. A chave de API e o ID do remetente são usados para verificar se o servidor que faz as solicitações tem permissão para enviar mensagens ao usuário de destino.

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

Lembre-se de que essa é uma mudança na API do FCM / GCM. Portanto, não é preciso atualizar as assinaturas, basta mudar o código do servidor para definir os cabeçalhos conforme mostrado acima.

Introdução do VAPID para identificação de servidores

VAPID é o novo nome curto para "Identificação voluntária do servidor de aplicativos" (em inglês). Basicamente, a nova especificação define um handshake entre o servidor do app e o serviço de push e permite que o serviço de push confirme qual site está enviando mensagens. Com a VAPID, é possível evitar etapas específicas do FCM para enviar mensagens push. Não é mais necessário ter um projeto do Firebase, um gcm_sender_id ou um cabeçalho Authorization.

O processo é muito simples:

  1. O servidor do aplicativo cria um par de chaves pública/privada. A chave pública é dada ao seu app da Web.
  2. Quando o usuário optar por receber push, adicione a chave pública ao objeto de opções da chamada Subscribe().
  3. Quando o servidor do app enviar uma mensagem push, inclua um JSON Web Token assinado com a chave pública.

Vamos analisar essas etapas em detalhes.

Criar um par de chaves pública/privada

Sou péssimo com criptografia, então aqui está a seção relevante das especificações sobre o formato das chaves públicas/privadas VAPID:

Os servidores de aplicativos DEVEM gerar e manter um par de chaves de assinatura utilizável com a assinatura digital de curva elíptica (ECDSA, na sigla em inglês) sobre a curva P-256.

Veja como fazer isso na biblioteca de nós de push da Web:

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

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

Como se inscrever com a chave pública

Para inscrever um usuário do Chrome para push com a chave pública VAPID, é necessário transmitir a chave pública como Uint8Array usando o parâmetro applicationServerKey do método Subscribe().

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

Você vai saber se ele funcionou examinando o endpoint no objeto de assinatura resultante. Se a origem for fcm.googleapis.com, isso significa que ele está funcionando.

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

Como enviar uma mensagem push

Para enviar uma mensagem usando VAPID, você precisa fazer uma solicitação normal do Web Push Protocol com dois cabeçalhos HTTP adicionais: um cabeçalho de autorização e um cabeçalho Crypto-Key.

Cabeçalho de autorização

O cabeçalho Authorization é um JSON Web Token (JWT) assinado com "WebPush" na frente.

Um JWT é uma maneira de compartilhar um objeto JSON com um segundo terceiro, de modo que a parte remetente possa assiná-lo e o destinatário possa verificar se a assinatura é do remetente esperado. A estrutura de um JWT é formada por três strings criptografadas, unidas por um ponto entre elas.

<JWTHeader>.<Payload>.<Signature>

Cabeçalho JWT

O cabeçalho JWT contém o nome do algoritmo usado para assinatura e o tipo de token. Para VAPID, precisa ser:

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

Em seguida, o URL é codificado em base64 e forma a primeira parte do JWT.

Payload

O payload é outro objeto JSON que contém o seguinte:

  • Público-alvo ("aud")
    • Esta é a origem do serviço de push (NÃO é a origem do seu site). Em JavaScript, você pode fazer o seguinte para conseguir o público-alvo: const audience = new URL(subscription.endpoint).origin
  • Tempo de validade ("exp")
    • Esse é o número de segundos até que a solicitação seja considerada expirada. Isso PRECISA estar dentro de 24 horas após a solicitação, em UTC.
  • Assunto ("sub")
    • O assunto precisa ser um URL ou um URL mailto:. Com isso, você tem um ponto de contato caso o serviço de push precise falar com o remetente da mensagem.

Um payload de exemplo pode ser parecido com este:

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

Esse objeto JSON tem codificação base64 para URL e forma a segunda parte do JWT.

Assinatura

A assinatura é o resultado da mesclagem do cabeçalho e do payload codificados com um ponto e, em seguida, criptografia do resultado usando a chave privada VAPID que você criou anteriormente. O resultado em si deve ser anexado ao cabeçalho com um ponto.

Não vou mostrar um exemplo de código para isso, já que há várias bibliotecas que usam os objetos JSON de cabeçalho e payload e geram essa assinatura para você.

O JWT assinado é usado como o cabeçalho de autorização com "WebPush" anexado a ele e será semelhante a este exemplo:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Observe algumas coisas a respeito disso. Primeiro, o cabeçalho de autorização literalmente contém a palavra "WebPush" e deve ser seguido por um espaço e, por fim, pelo JWT. Observe também os pontos que separam o cabeçalho, o payload e a assinatura do JWT.

Cabeçalho Crypto-Key

Além do cabeçalho de autorização, é preciso adicionar sua chave pública VAPID ao cabeçalho Crypto-Key como uma string codificada pelo URL base64 com p256ecdsa= precedido a ela.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

Ao enviar uma notificação com dados criptografados, você já estará usando o cabeçalho Crypto-Key. Portanto, para adicionar a chave do servidor de aplicativos, basta adicionar um ponto e vírgula antes de adicionar o conteúdo acima, resultando em:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

Verdade sobre essas mudanças

Com a VAPID, não é mais necessário se inscrever em uma conta com o GCM para usar push no Chrome e usar o mesmo caminho de código para inscrever um usuário e enviar uma mensagem para ele no Chrome e no Firefox. Ambos estão seguindo os padrões.

O que você precisa ter em mente é que, no Chrome 51 e em versões anteriores, no Opera para navegadores Android e Samsung, ainda será necessário definir gcm_sender_id no manifesto do app da Web e adicionar o cabeçalho de autorização ao endpoint do FCM que será retornado.

A VAPID oferece uma rampa de acesso a esses requisitos reservados. Se você implementar o VAPID, ele funcionará em todos os navegadores compatíveis com push na Web. À medida que mais navegadores oferecem suporte à VAPID, você pode decidir quando remover a gcm_sender_id do manifesto.

.