Éxitos de la interoperabilidad push web

Joe Medley
Joe Medley

Cuando Chrome comenzó a admitir la API de notificaciones web, dependía del servicio de notificaciones push de Firebase Cloud Messaging (FCM), antes conocido como Google Cloud Messaging (GCM). Esto requería el uso de su API propia. Esto permitió que Chrome pusiera la API de Web Push a disposición de los desarrolladores en un momento en el que aún se estaba escribiendo la especificación del Protocolo de Web Push y, más adelante, proporcionó autenticación (es decir, que el remitente del mensaje es quien dice ser) en un momento en el que el Protocolo de Web Push carecía de ella. ¡Buenas noticias! Ninguna de estas opciones es verdadera.

FCM / GCM y Chrome ahora admiten el protocolo de notificaciones de aplicaciones web estándar, mientras que la autenticación del remitente se puede lograr mediante la implementación de VAPID, lo que significa que tu app web ya no necesita un "gcm_sender_id".

En este artículo, primero describiré cómo convertir tu código de servidor existente para usar el Protocolo de notificaciones push web con FCM. A continuación, te mostraré cómo implementar VAPID en el código de tu cliente y del servidor.

FCM admite el protocolo web push

Comencemos con un poco de contexto. Cuando tu aplicación web se registra para una suscripción push, se le proporciona la URL de un servicio push. El servidor usará este extremo para enviar datos al usuario a través de la app web. En Chrome, se te otorgará un extremo de FCM si suscribes a un usuario sin VAPID. (hablaremos sobre VAPID más adelante). Antes de que FCM admitiera el protocolo push web, debías extraer el ID de registro de FCM del final de la URL y colocarlo en el encabezado antes de hacer una solicitud a la API de FCM. Por ejemplo, un extremo de FCM de https://android.googleapis.com/gcm/send/ABCD1234 tendría un ID de registro de “ABCD1234”.

Ahora que FCM admite el Protocolo de notificaciones web, puedes dejar el extremo intacto y usar la URL como un extremo del Protocolo de notificaciones web. (Esto lo alinea con Firefox y, con suerte, con todos los demás navegadores futuros).

Antes de analizar VAPID, debemos asegurarnos de que nuestro código del servidor controle correctamente el extremo de FCM. A continuación, se muestra un ejemplo de cómo realizar una solicitud a un servicio de push en Node. Ten en cuenta que, para FCM, agregamos la clave de API a los encabezados de la solicitud. Para otros extremos de servicios push, no será necesario. En el caso de Chrome anterior a la versión 52, Opera para Android y el navegador Samsung, también debes incluir un "gcm_sender_id" en el archivo manifest.json de tu app web. La clave de API y el ID del remitente se usan para verificar si el servidor que realiza las solicitudes realmente puede enviar mensajes al usuario receptor.

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

Recuerda que este es un cambio en la API de FCM o GCM, por lo que no necesitas actualizar tus suscripciones. Solo cambia el código del servidor para definir los encabezados como se muestra más arriba.

Presentamos VAPID para la identificación de servidores

VAPID es el nuevo nombre corto para "Identificación voluntaria del servidor de aplicaciones". En esencia, esta especificación nueva define un protocolo de enlace entre tu servidor de apps y el servicio push, y permite que este último confirme qué sitio está enviando mensajes. Con VAPID, puedes evitar los pasos específicos de FCM para enviar un mensaje push. Ya no necesitas un proyecto de Firebase, un encabezado gcm_sender_id o Authorization.

El proceso es bastante sencillo:

  1. El servidor de tu aplicación crea un par de claves pública/privada. La clave pública se le proporciona a tu app web.
  2. Cuando el usuario elija recibir notificaciones push, agrega la clave pública al objeto de opciones de la llamada subscribe().
  3. Cuando el servidor de tu app envíe un mensaje push, incluye un token web JSON firmado junto con la clave pública.

Analicemos estos pasos en detalle.

Crea un par de claves pública/privada

No sé mucho de encriptación, así que aquí tienes la sección relevante de las especificaciones con respecto al formato de las claves pública y privada de VAPID:

Los servidores de aplicaciones DEBEN generar y mantener un par de claves de firma que se pueda usar con la firma digital de curva elíptica (ECDSA) en la curva P-256.

Puedes ver cómo hacerlo en la biblioteca de nodos web-push:

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

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

Cómo suscribirse con la clave pública

Para suscribir a un usuario de Chrome para notificaciones push con la clave pública de VAPID, debes pasar la clave pública como un Uint8Array con el parámetro applicationServerKey del método subscribe().

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

Para saber si funcionó, examina el extremo del objeto de suscripción resultante. Si el origen es fcm.googleapis.com, significa que está funcionando.

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

Cómo enviar un mensaje push

Para enviar un mensaje con VAPID, debes realizar una solicitud normal del protocolo Web Push con dos encabezados HTTP adicionales: un encabezado de Authorization y un encabezado de Crypto-Key.

Encabezado de autorización

El encabezado Authorization es un token web JSON (JWT) firmado con "WebPush" al principio.

Un JWT es una forma de compartir un objeto JSON con una segunda parte de manera que la parte emisora pueda firmarlo y la parte receptora pueda verificar que la firma sea del emisor esperado. La estructura de un JWT consta de tres cadenas encriptadas, unidas con un solo punto entre ellas.

<JWTHeader>.<Payload>.<Signature>

Encabezado JWT

El encabezado JWT contiene el nombre del algoritmo que se usa para la firma y el tipo de token. Para VAPID, debe ser lo siguiente:

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

Luego, se codifica en base64 y forma la primera parte del JWT.

Carga útil

La carga útil es otro objeto JSON que contiene lo siguiente:

  • Público ("aud")
    • Este es el origen del servicio push (NO el origen de tu sitio). En JavaScript, puedes hacer lo siguiente para obtener el público: const audience = new URL(subscription.endpoint).origin
  • Hora de vencimiento (“exp”)
    • Es la cantidad de segundos hasta que se considere que la solicitud caducó. DEBEN realizarse en un plazo de 24 horas después de que se realice la solicitud, en UTC.
  • Asunto ("sub")
    • El asunto debe ser una URL o una URL de mailto:. Esto proporciona un punto de contacto en caso de que el servicio push necesite comunicarse con el remitente del mensaje.

Una carga útil de ejemplo podría verse de la siguiente manera:

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

Este objeto JSON está codificado en base64url y forma la segunda parte del JWT.

Firma

La firma es el resultado de unir el encabezado codificado y la carga útil con un punto y, luego, encriptar el resultado con la clave privada de VAPID que creaste antes. El resultado en sí se debe agregar al encabezado con un punto.

No mostraré una muestra de código para esto, ya que hay una serie de bibliotecas que tomarán los objetos JSON del encabezado y la carga útil y generarán esta firma por ti.

El JWT firmado se usa como encabezado de autorización con "WebPush" agregado al principio y se verá de la siguiente manera:

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

Ten en cuenta algunos aspectos sobre esto. En primer lugar, el encabezado de Authorization contiene literalmente la palabra "WebPush" y debe estar seguido de un espacio y, luego, del JWT. También observa los puntos que separan el encabezado, la carga útil y la firma del JWT.

Encabezado de Crypto-Key

Además del encabezado de autorización, debes agregar tu clave pública de VAPID al encabezado Crypto-Key como una cadena codificada en base64 con p256ecdsa= al principio.

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

Cuando envíes una notificación con datos encriptados, ya estarás usando el encabezado Crypto-Key, por lo que, para agregar la clave del servidor de aplicaciones, solo debes agregar un punto y coma antes de agregar el contenido anterior, lo que generará lo siguiente:

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

Realidad de estos cambios

Con VAPID, ya no es necesario registrarse para obtener una cuenta en GCM para usar notificaciones push en Chrome, y puedes usar la misma ruta de código para suscribir a un usuario y enviarle un mensaje en Chrome y Firefox. Ambos cumplen con los estándares.

Lo que debes tener en cuenta es que, en Chrome 51 y versiones anteriores, en los navegadores Opera para Android y Samsung, deberás definir el gcm_sender_id en el manifiesto de tu app web y deberás agregar el encabezado de autorización al extremo de FCM que se mostrará.

VAPID proporciona una salida de estos requisitos propietarios. Si implementas VAPID, funcionará en todos los navegadores que admitan el envío web. A medida que más navegadores admitan VAPID, podrás decidir cuándo quitar el gcm_sender_id de tu manifiesto.