ウェブプッシュの相互運用性の成功事例

Chrome が最初にウェブ プッシュ API をサポートしたとき、Firebase Cloud Messaging(FCM)(旧称 Google Cloud Messaging(GCM))プッシュ サービスに依存していました。これには、独自の API の使用が必要でした。これにより、Chrome では、Web Push Protocol の仕様が記述されている最中にデベロッパーが Web Push API を使用できるようにし、その後、Web Push Protocol に API が組み込まれていないときに認証(つまり、メッセージの送信者が行う本人)を認証できるようになりました。幸いなことに、どちらも事実ではありません。

FCM / GCM と Chrome は標準の ウェブ プッシュ プロトコルをサポートするようになりました。また、送信者の認証は VAPID を実装することで実現できるため、ウェブアプリで「gcm_sender_id」が必要になることはなくなりました。

この記事では、まず、既存のサーバーコードを変換して、FCM でウェブプッシュ プロトコルを使用する方法について説明します。次はクライアントコードとサーバーコードの両方に VAPID を実装する方法を紹介します

FCM がウェブ push プロトコルをサポートしている

少し背景を説明しましょう。ウェブ アプリケーションがプッシュ サブスクリプションを登録すると、プッシュ サービスの URL が提供されます。サーバーは、このエンドポイントを使用してウェブアプリを介してユーザーにデータを送信します。Chrome では、VAPID を使用せずにユーザーを登録すると、FCM エンドポイントが提供されます。(VAPID については後で説明します)。FCM がウェブプッシュ プロトコルをサポートする前は、FCM API リクエストを行う前に、URL から FCM 登録 ID を抽出してヘッダーに挿入する必要がありました。たとえば、FCM エンドポイントが https://android.googleapis.com/gcm/send/ABCD1234 の場合、登録 ID は「ABCD1234」になります。

FCM が Web Push プロトコルをサポートしているため、エンドポイントをそのままにして、URL を Web Push プロトコル エンドポイントとして使用できます。(これにより、Firefox と、将来的には他のすべてのブラウザと整合するようになります)。

VAPID について詳しく説明する前に、サーバーコードが FCM エンドポイントを正しく処理していることを確認する必要があります。Node の push サービスにリクエストを送信する例を次に示します。FCM では、リクエスト ヘッダーに API キーを追加しています。他の push サービス エンドポイントでは、これは必要ありません。バージョン 52 より前の Chrome、Opera Android、Samsung ブラウザでは、ウェブアプリの manifest.json に「gcm_sender_id」を含める必要があります。API キーと送信者 ID は、リクエストを送信するサーバーが受信ユーザーにメッセージを送信することを実際に許可されているかどうかを確認するために使用されます。

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

これは FCM / GCM の API の変更であるため、定期購入を更新する必要はありません。上記のようにヘッダーを定義するようにサーバーコードを変更するだけです。

サーバー識別用の VAPID の導入

VAPID は、Voluntary Application Server Identification の新しい略称です。この新しい仕様では、基本的にアプリサーバーとプッシュ サービス間のハンドシェイクが定義され、プッシュ サービスがどのサイトがメッセージを送信しているかを確認できるようになります。VAPID を使用すると、プッシュ メッセージを送信するための FCM 固有の手順を回避できます。Firebase プロジェクト、gcm_sender_idAuthorization ヘッダーは不要になりました。

手順は非常にシンプルです。

  1. アプリケーション サーバーで公開鍵/秘密鍵のペアが作成されます。公開鍵がウェブアプリに渡されます。
  2. ユーザーがプッシュの受信を選択したら、subscribe() 呼び出しのオプション オブジェクトに公開鍵を追加します。
  3. アプリサーバーがプッシュ メッセージを送信する場合は、署名付き JSON Web Token と公開鍵を一緒に含めます。

これらのステップを詳しく見ていきましょう。

公開鍵/秘密鍵のペアを作成する

私は暗号化が苦手なので、VAPID の公開鍵/秘密鍵の形式に関する仕様の関連セクションを以下に示します。

アプリサーバーは、P-256 曲線上で楕円曲線デジタル署名(ECDSA)で使用できる署名鍵ペアを生成して維持すべきです。

方法については、ウェブ プッシュ ノード ライブラリをご覧ください。

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

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

公開鍵による定期購入

VAPID 公開鍵で Chrome ユーザーをプッシュ配信に登録するには、subscribe() メソッドの applicationServerKey パラメータを使用して、公開鍵を Uint8Array として渡す必要があります。

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

正常に機能しているかどうかは、結果の定期購入オブジェクトのエンドポイントを調べることで確認できます。オリジンが fcm.googleapis.com であれば、正常に機能しています。

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

プッシュ メッセージの送信

VAPID を使用してメッセージを送信するには、Authorization ヘッダーと Crypto-Key ヘッダーという 2 つの追加の HTTP ヘッダーを使用して、通常のウェブプッシュ プロトコル リクエストを行う必要があります。

認証ヘッダー

Authorization ヘッダーは、前に「WebPush」が付いた署名付き JSON Web Token(JWT)です。

JWT は、想定した送信者からの署名であることを受信者が検証できるように、送信者が署名して、JSON オブジェクトを受信者と共有する方法です。JWT の構造は、3 つの暗号化された文字列をドットで区切ったものです。

<JWTHeader>.<Payload>.<Signature>

JWT ヘッダー

JWT ヘッダーには、署名に使用されるアルゴリズム名とトークンのタイプが含まれます。VAPID の場合、次のようにする必要があります。

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

その後、base64 url でエンコードされ、JWT の最初の部分を形成します。

ペイロード

Payload は、以下を含む別の JSON オブジェクトです。

  • オーディエンス(「aud」)
    • これはプッシュ サービスの送信元です(サイトの送信元ではない)。JavaScript では、次のようにしてオーディエンスを取得できます。const audience = new URL(subscription.endpoint).origin
  • 有効期限(「exp」)
    • これは、リクエストが期限切れと見なされるまでの秒数です。これは、リクエストから 24 時間以内(UTC)に行う必要があります。
  • 件名(「sub」)
    • サブジェクトは URL または mailto: URL である必要があります。これにより、プッシュ サービスがメッセージの送信者に連絡する必要がある場合に連絡先が提供されます。

ペイロードの例を次に示します。

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

この JSON オブジェクトは base64 URL エンコードされ、JWT の 2 番目の部分を形成します。

署名

署名は、エンコードされたヘッダーとペイロードをドットで結合し、前に作成した VAPID 秘密鍵を使用して結果を暗号化した結果です。結果自体は、ドットでヘッダーに追加する必要があります。

ヘッダーとペイロードの JSON オブジェクトを受け取ってこの署名を生成する多数のライブラリがあるため、コードサンプルは示しません。

署名付き JWT は、Authorization ヘッダーとして使用され、「WebPush」が先頭に付加されます。次のような形式になります。

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

次の点に注意してください。まず、Authorization ヘッダーには「WebPush」という単語が文字通り含まれており、その後にスペースを挿入して JWT を追加する必要があります。また、JWT のヘッダー、ペイロード、署名を区切るドットにも注目してください。

Crypto-Key ヘッダー

Authorization ヘッダーに加えて、Crypto-Key ヘッダーに VAPID 公開鍵を base64 URL エンコード文字列として追加し、p256ecdsa= を先頭に追加する必要があります。

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

暗号化されたデータを含む通知を送信する場合は、すでに Crypto-Key ヘッダーを使用しているため、アプリケーション サーバー鍵を追加するには、上記のコンテンツを追加する前にセミコロンを追加するだけで済みます。結果は次のようになります。

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

これらの変化の現実

VAPID を使用すると、Chrome でプッシュを使用するために GCM のアカウントに登録する必要がなくなります。また、Chrome と Firefox の両方で、ユーザーの登録とユーザーへのメッセージ送信に同じコードパスを使用できます。どちらも標準に準拠しています。

ただし、Chrome 51 以前、Android 版 Opera、Samsung ブラウザでは、ウェブアプリ マニフェストで gcm_sender_id を定義し、返される FCM エンドポイントに Authorization ヘッダーを追加する必要があります。

VAPID は、こうした独自の要件からの運用開始を可能にします。VAPID を実装すると、ウェブプッシュをサポートするすべてのブラウザで機能します。より多くのブラウザが VAPID をサポートするにつれて、マニフェストから gcm_sender_id を削除するタイミングを決めることができます。