Web PushをFCMとVAPIDで認証してブラウザにプッシュ通知を送る
Web Pushを試している。調べていく過程で2つの認証方式を用いてプッシュ通知を送信できることが分かった。1つはFirebase Cloud Messaging(FCM)を使い取得したサーバーキーを認証に使い送信する方法とVoluntary Application Server Identification for Web Push (VAPID)で認証をする方法である。
2つの方法としたがWeb Pushが標準化する過程で整理された認証方法であり、VAPIDのほうが後発となりFirebaseのサーバーキーを必要としない認証方式である。 VAPIDはFirebaseのプロジェクト登録が不要となるだけでプッシュサーバはFirebase Cloud Messagingが担っている。
今回のエントリではFCMとVAPIDそれぞれのWeb Pushのプッシュ通知方法をまとめていく。 また試したブラウザはChromeのみである。
Firebase Cloud Messaging(FCM)のWeb Push
事前にFirebaseでプロジェクトを作成しサーバキーと送信者IDが必要となる。
※プロジェクトを作成済みであればFirebaseのコンソールから「Overview」→「プロジェクトの設定」→「クラウドメッセージング」からそれぞれ参照できる。
ここからはServiceWorkerなどフロントエンド(クライアント)とサーバに分けてプッシュ通知方法をまとめていく。
クライアント
クライアントはServiceWorkerの登録とWeb Push購読時に取得できる各種変数をサーバ側へ送信する。実際にプッシュサーバへプッシュ通知をリクエストするのはサーバである。
manifest.json
Firebaseプロジェクトから取得した送信者IDはmanifest.jsonで利用するので登録しておく。
| 1 2 3 4 5 | {
  "name": "FCM Web-Push",
・・・省略
  "gcm_sender_id": "Firebaseプロジェクトから取得した送信者ID"
} | 
ServiceWorker登録イベントとWeb Push購読イベント
クライアント側ではServiceWorkerを登録して、ブラウザでWeb Pushの購読が完了するとエンドポイントとPayload を暗号化するためのブラウザの公開鍵と鍵生成の複雑生成を増すための乱数が取得できる。これらの変数をサーバへ送信する。サーバはその鍵を利用することで通知メッセージを暗号化してPayloadに乗せることができる。
|  |  | 
subscriptionにエンドポイントとブラウザの公開鍵(p256dh)、乱数(auth)が格納されている。
プッシュ通知送信時にサーバにエンドポイントとブラウザの公開鍵(p256dh)、乱数(auth)を送信する
|  |  | 
プッシュ通知受信イベント時の処理
プッシュ通知を受け取ったイベントはServiceWorkerで処理する
|  |  | 
- event.dataにサーバ側から暗号化されたPayloadが格納されている。- json()をコールすることでJSONフォーマットで整形される。
- notificationClick(event)では通知がクリックされた時のイベントをまとめてある。- showNotification(data)で- dataにURLなどを指定しておけば- notificationClick(event)で参照できる。
サーバ
サーバ側ではクライアントから送信されたエンドポイントとブラウザの公開鍵(p256dh)、乱数(auth)をもとに通知メッセージを暗号化する。
暗号化のライブラリはMartijnDwars/web-pushをつかった。
暗号化はライブラリがほとんど処理してくれるためサーバ側のコードはシンプルである。
|  |  | 
push.setGcmApiKey(appProperties.serverKey) でFirebaseで取得したサーバキーを指定している。
暗号化の詳細については次のエントリが参考になるのでオススメする。
Web Pushでブラウザにプッシュ通知を送ってみる - Qiita
プッシュサーバへ送信時のヘッダーとエンドポイントURL
次のVAPIDのWeb Pushと比較したいためプッシュサーバ送信時のヘッダーとエンドポイントURL をまとめていきたい。
| 1 2 3 4 | -H "Authorization: key={Firebaseのサーバキー}" \
-H "Encryption: keyid=p256dh;salt={乱数、salt}" \
-H "Crypto-Key: keyid=p256dh;dh={共有鍵}" \
-H "Ttl: 2419200" | 
AuthorizationにはFirebaseのサーバキーを指定している。クライアントから送信された公開鍵とauthからEncryptionとCrypto-Keyを生成している。 ブラウザでは暗号化されたPayloadを復号する。
| 1
 | エンドポイントURL: https://android.googleapis.com/gcm/send/{registration_id} | 
エンドポイントURLのOriginはGoogle Cloud Messaging(GCM)である。
サンプルコード
これまでFCMのWeb Pushをまとめてきたがコードの断片のみで参考にならない。 動作確認ができるコード一式をgithubに公開しているので参照してほしい。
VAPIDのWeb Push
つぎにVAPIDのWeb Pushをまとめていこう。VAPIDの全体の流れは次のエントリが参考になるのでオススメする。(同じ作者である。一貫してまとめていただいているので大変助かりました。)
GCMの登録が不要になったChromeのWeb Pushを試してみる - Qiita
FCMのほうではFirebaseのプロジェクト登録が必要であったがVAPIDでは必要としない。 クライアント側ではsubscription取得時にサーバ側から取得した公開鍵を用いる。
ここからはクライアントとサーバに分けてFCMとの違いについてまとめていく。
クライアント
クライアントはWeb Push購読時の処理に変更が入っている。
| 1 2 3 4 5 6 7 8 9 10 11 12 | function requestPushSubscription(registration) {
    return fetch(appServerPublicKeyURL).then(function(response) {
        return response.text();
    }).then(function(key) {
        let opt = {
            userVisibleOnly: true,
            applicationServerKey: decodeBase64URL(key)
        };
        return registration.pushManager.subscribe(opt).then(getSubscription, errorSubscription);
    });
} | 
サーバ側から公開鍵を取得し購読リクエストに含めている。
そのほかの変更点はmanifest.jsonからgcm_sender_idが取り除かれたのみである。
サーバ
サーバ側では公開鍵をクライアントに提供するためのAPIを追加している。
| 1 2 3 4 | @GetMapping("public-key")
fun get(): String {
    return publicKey
} | 
暗号化のライブラリはFCMと同じくMartijnDwars/web-pushをつかっている。VAPIDもサポートしてくれている。
通知処理のAPIには公開鍵と秘密鍵を含めてPushServiceオブジェクトを生成している。
| 1 2 3 4 5 6 7 8 9 10 11 | @PostMapping
fun post(@RequestBody req: Request): ResponseEntity<Boolean> {
    val payload  = objectMapper().writeValueAsString(Payload(req.message, req.tag, req.icon, req.url))
    Security.addProvider(BouncyCastleProvider())
    val push = app.push.PushService(publicKey, privateKey, "http://localhost")
    push.send(Notification(req.endpoint, req.key, req.auth, payload))
    return ok().json().body(true)
} | 
プッシュサーバへ送信時のヘッダーとエンドポイントURL
VAPIDに変わると次のようにヘッダーとエンドポイントが変わっている。
| 1 2 3 4 5 | -H "Authorization: WebPush {JWT形式の署名トークン}" \
-H "Encryption: keyid=p256dh;salt={乱数、salt ※ここは変わらない}" \
-H "Crypto-Key: keyid=p256dh;dh={共有鍵};p256ecdsa={サーバの公開鍵}" \
-H "Content-Type: application/octet-stream" \
-H "Ttl: 2419200" \ | 
AuthorizationとCrypto-Keyにそれぞれ変更と追加がある。
| 1
 | エンドポイントURL: https://fcm.googleapis.com/fcm/send/{registration_id} | 
エンドポイントURLのOriginはFirebase Cloud Messagingに変更されている。
サンプルコード
同様にVAPIDのほうもgithubにコード一式を公開したので参照してほしい。
まとめ
- FCMとVAPIDのWeb Pushのプッシュ通知方法が理解できた。エンドポイントなどをサービス側で保持するタイミングなど実運用に向けて考えなくてはいけないことがある。
- Chromeのみ動作確認を行っていたが各種ブラウザの挙動が異なりそうなので導入時にクリアしていきたい。
関連エントリ
FCMでWeb Push。Firebase Javascript SDKを使ったプッシュ通知とトピック送信を試した。 - 平日インプット週末アウトプットぶろぐ
