Quando o Chrome começou a oferecer suporte à API Web Push, ele dependia do serviço de push do Firebase Cloud Messaging (FCM, na sigla em inglês), anteriormente conhecido como Google Cloud Messaging (GCM, na sigla em inglês). Isso exigia o uso da API reservada. Isso permitiu que o Chrome disponibilizasse a API Web Push para os desenvolvedores em um momento em que a especificação do protocolo Web Push ainda estava sendo escrita e, mais tarde, forneceu autenticação (ou seja, o remetente da mensagem é quem diz ser) em um momento em que o protocolo Web Push não tinha isso. Boa notícia: nenhuma dessas afirmações é verdadeira mais.
O FCM / GCM e o Chrome agora oferecem suporte ao protocolo de push na Web padrão, e a autenticação do remetente pode ser feita com a implementação do VAPID. Isso significa que seu app da Web não precisa mais de um "gcm_sender_id".
Neste artigo, vamos primeiro descrever como converter seu código do servidor para usar o protocolo de push da Web com o FCM. A seguir, vou mostrar como implementar o VAPID no código do cliente e do servidor.
O FCM oferece suporte ao protocolo de push da Web
Vamos começar com um pouco de contexto. Quando seu aplicativo da Web se registra para uma
assinatura push, ele recebe o URL de um serviço push. Seu servidor vai usar
esse endpoint para enviar dados ao usuário pelo seu app da Web. No Chrome, você vai receber
um endpoint do FCM se assinar um usuário sem VAPID. Vamos abordar o VAPID
mais tarde. Antes do FCM oferecer suporte ao protocolo de push da Web, era necessário extrair o ID de registro
do FCM do final do URL e colocá-lo no cabeçalho antes
de fazer uma solicitação da API do FCM. Por exemplo, um endpoint do FCM de
https://android.googleapis.com/gcm/send/ABCD1234
teria um ID de registro
de "ABCD1234".
Agora que o FCM oferece suporte ao protocolo Web Push, você pode deixar o endpoint intacto e usar o URL como um endpoint do protocolo Web Push. Isso está alinhado com o Firefox e, esperamos, com todos os outros navegadores futuros.
Antes de mergulhar no VAPID, precisamos garantir que o código do servidor processe corretamente o endpoint do FCM. Confira abaixo um exemplo de solicitação para um serviço de push no Node. Observe que, para o FCM, estamos adicionando a chave de API aos cabeçalhos de solicitação. Para outros endpoints de serviço de push, isso não será necessário. Para o Chrome anterior à versão 52, Opera Android e o navegador Samsung, ainda é necessário incluir um "gcm_sender_id" no manifest.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 que recebe.
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 é necessário atualizar as assinaturas, basta mudar o código do servidor para definir os cabeçalhos, conforme mostrado acima.
Introdução ao VAPID para identificação do servidor
VAPID é o novo nome abreviado de
"Identificação voluntária do servidor de aplicativos". Essa
nova especificação define essencialmente um handshake entre o servidor do app e o serviço
push e permite que o serviço push confirme qual site está enviando mensagens.
Com o VAPID, você pode evitar as etapas específicas do FCM para enviar uma mensagem push. Você
não precisa mais de um projeto do Firebase, um gcm_sender_id
ou um
cabeçalho Authorization
.
O processo é bem simples:
- O servidor do aplicativo cria um par de chaves pública/privada. A chave pública é fornecida ao seu app da Web.
- Quando o usuário escolher receber pushes, adicione a chave pública ao objeto de opções da chamada subscribe().
- 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
Não sou muito bom em criptografia, então aqui está a seção relevante da especificação sobre o formato das chaves públicas/privadas VAPID:
Os servidores de aplicativos precisam gerar e manter um par de chaves de assinatura que possa ser usado com a assinatura digital de curva elíptica (ECDSA) na curva P-256.
Confira como fazer isso na biblioteca de nós de push web:
function generateVAPIDKeys() {
var curve = crypto.createECDH('prime256v1');
curve.generateKeys();
return {
publicKey: curve.getPublicKey(),
privateKey: curve.getPrivateKey(),
};
}
Como assinar com a chave pública
Para assinar um usuário do Chrome para push com a chave pública VAPID, é necessário transmitir
a chave pública como um 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
, ele vai funcionar.
https://fcm.googleapis.com/fcm/send/ABCD1234
Como enviar uma mensagem push
Para enviar uma mensagem usando o VAPID, você precisa fazer uma solicitação normal do protocolo Web Push com dois cabeçalhos HTTP adicionais: um cabeçalho de autorização e um de chave criptográfica.
Cabeçalho de autorização
O cabeçalho Authorization
é um token JSON Web (JWT) assinado com "WebPush" na frente.
Um JWT é uma forma de compartilhar um objeto JSON com um intermediário, de forma que a parte remetente possa assiná-lo e o destinatário possa verificar se a assinatura é do remetente esperado. A estrutura de um JWT é composta por três strings criptografadas, unidas por um único ponto entre elas.
<JWTHeader>.<Payload>.<Signature>
Cabeçalho JWT
O cabeçalho JWT contém o nome do algoritmo usado para a assinatura e o tipo de token. Para VAPID, precisa ser:
{
"typ": "JWT",
"alg": "ES256"
}
Em seguida, ele é codificado em base64url e forma a primeira parte do JWT.
Payload
O payload é outro objeto JSON que contém o seguinte:
- Público-alvo ("aud")
- Essa é a origem do serviço push (NÃO a origem do seu site).
Em JavaScript, você pode fazer o seguinte para receber o público-alvo:
const audience = new URL(subscription.endpoint).origin
- Essa é a origem do serviço push (NÃO a origem do seu site).
Em JavaScript, você pode fazer o seguinte para receber o público-alvo:
- Prazo de validade ("exp")
- É o número de segundos até que a solicitação seja considerada expirada. Isso PRECISA ser feito em até 24 horas após o pedido, no UTC.
- Assunto ("sub")
- O assunto precisa ser um URL ou um URL
mailto:
. Isso fornece um ponto de contato caso o serviço de push precise entrar em contato com o remetente da mensagem.
- O assunto precisa ser um URL ou um URL
Um exemplo de payload 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 é codificado em base64url e forma a segunda parte do JWT.
Assinatura
A assinatura é o resultado da união do cabeçalho codificado e do payload com um ponto e, em seguida, criptografa o resultado usando a chave privada VAPID criada anteriormente. O resultado em si precisa ser anexado ao cabeçalho com um ponto.
Não vou mostrar um exemplo de código para isso, porque há várias bibliotecas que vão usar os objetos JSON de cabeçalho e payload e gerar essa assinatura para você.
O JWT assinado é usado como o cabeçalho de autorização com "WebPush" na frente e tem a seguinte aparência:
WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A
Observe algumas coisas sobre isso. Primeiro, o cabeçalho de autorização literalmente contém a palavra "WebPush" e deve ser seguido por um espaço e 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, adicione sua chave pública VAPID ao cabeçalho
Crypto-Key
como uma string codificada em base64 com p256ecdsa=
adicionada no início.
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo
Quando você envia uma notificação com dados criptografados, você já
está usando o cabeçalho Crypto-Key
. Para adicionar a chave do servidor do aplicativo,
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
Realidade dessas mudanças
Com o VAPID, não é mais necessário se inscrever em uma conta do GCM para usar push no Chrome. Você pode usar o mesmo caminho de código para inscrever um usuário e enviar uma mensagem a ele no Chrome e no Firefox. Ambos estão seguindo os padrões.
É importante lembrar que no Chrome 51 e versões anteriores, no Opera para
Android e no navegador Samsung, ainda será necessário definir o gcm_sender_id
no manifesto do app da Web e adicionar o cabeçalho de autorização ao
endpoint do FCM que será retornado.
O VAPID oferece uma saída para esses requisitos proprietários. Se você implementar
o VAPID, ele vai funcionar em todos os navegadores que oferecem suporte a push da Web. À medida que mais navegadores
oferecerem suporte ao VAPID, você poderá decidir quando remover o gcm_sender_id
do
manifesto.