
【iOS】コマンドを使ってPush通知を送信する方法

IT技術

はじめに
最近、iOS の Push 通知の実装を修正したのですが、その際、設定がうまくいっているか確認するため、コマンドを使ってPush通知を送信しました。
今回、コマンドを使ってPush通知を送信する方法を備忘録としてまとめたいと思います。
- 参考
証明書(.p12)を使用して Push 通知を送信
iOS の Push 通知には、トークン(.p8)を使用しての通知送信と証明書(.p12)を使用しての通知送信があります。
まずは、証明書(.p12)を使用しての通知送信についてです。
- 参考
P12 形式のファイルを PEM 形式に変換する
Push 通知の証明書からキーを書き出す際に P12 形式で書き出しますが、コマンド送信の際には、P12 形式を使用することができないため、PEM 形式への変換が必要になります。
以下のコマンドを実行し、P12 形式から PEM 形式へ変更を行います。
1openssl pkcs12 -in $HOME/my-project-push.p12 -out $HOME/my-project-push.pem -legacy -nodes
環境変数の設定
Push 通知を送信する際に必要な情報は、あらかじめ環境変数に設定しておきます。
1CERTIFICATE_FILE_NAME='$HOME/my-project-push.cer' // プッシュ通知証明書のパスを設定
2CERTIFICATE_KEY_FILE_NAME='$HOME/my-project-push.pem' // P12形式から変換したPEM形式のファイルパスを設定
3TOPIC='com.myProject' // アプリのBundleIDを設定
4DEVICE_TOKEN='1234567890...' // アプリで発行されるデバイストークンを設定
5APNS_HOST_NAME='api.sandbox.push.apple.com' // APNsのホスト名を設定。開発環境と本番環境で異なる
APNS_HOST_NAME には APNs サーバーのホスト名を設定します。
APNs には開発サーバーと本番サーバーがあり、以下のいずれかを設定します。
- 開発サーバー:api.sandbox.push.apple.com
- 本番サーバー:api.push.apple.com
基本的に Xcode で実機ビルドした場合はapi.sandbox.push.apple.com
、TestFlight で実機インストールした場合はapi.push.apple.com
になるかと思います。
- 参考:
APNs への接続確認
以下のコマンドを実行し、APNs へ問題なく接続できることを確認します。
1openssl s_client \
2-connect "${APNS_HOST_NAME}":443 \
3-cert "${CERTIFICATE_FILE_NAME}" \
4-certform DER \
5-key "${CERTIFICATE_KEY_FILE_NAME}" \
6-keyform PEM
特にエラーなど表示されず、Verify return code: 0 (ok)
が表示されれば OK です。
Push 通知の送信
APNs の接続確認ができたら、以下のコマンドで Push 通知を送信します。
1curl -v \
2--header "apns-topic: ${TOPIC}" \
3--header "apns-push-type: alert" \
4--cert "${CERTIFICATE_FILE_NAME}" \
5--cert-type DER \
6--key "${CERTIFICATE_KEY_FILE_NAME}" \
7--key-type PEM \
8--data '{"aps":{"alert":"test"}}' \
9--http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
特に問題なければ HTTP ステータス 200 で通知が送信されます。
トークン(.p8)を使用して Push 通知を送信
次に、トークン(.p8)を使用しての通知送信についてです。
- 参考
P8 形式のファイルを PEM 形式に変換する
コマンド送信の際には P8 形式を使用することができないため、PEM 形式への変換が必要になります。
以下のコマンドを実行し、P8 形式から PEM 形式へ変更を行います。
1openssl pkcs8 -in $HOME/AuthKey_KeyID.p12 -out $HOME/AuthKey_KeyID.pem -nocrypt
環境変数の設定
Push 通知を送信する際に必要な情報は、あらかじめ環境変数に設定しておきます。
1TEAM_ID=Team ID // AppleDeveloperアカウントのチームID
2TOKEN_KEY_FILE_NAME='$HOME/AuthKey_KeyID.pem' // P8形式から変換したPEM形式のファイルパスを設定
3AUTH_KEY_ID=Key ID // キーID(.p8のキーID)
4TOPIC='com.myProject' // アプリのBundleIDを設定
5DEVICE_TOKEN='1234567890...' // アプリで発行されるデバイストークンを設定
6APNS_HOST_NAME='api.sandbox.push.apple.com' // APNsのホスト名を設定。開発環境と本番環境で異なる
TEAM_ID には Apple Developer アカウントのチーム ID を設定します。
Apple Developer アカウントページの「メンバーシップの詳細」から確認することができます。
AUTH_KEY_ID には、.p8 のキー ID を設定します。
Certificates, Identifiers & Profilesのサイドバーにある「Keys」で確認することができます。
APNS_HOST_NAME には APNs サーバーのホスト名を設定します。
APNs には開発サーバーと本番サーバーがあり、以下のいずれかを設定します。
- 開発サーバー:api.sandbox.push.apple.com
- 本番サーバー:api.push.apple.com
基本的に Xcode で実機ビルドした場合はapi.sandbox.push.apple.com
、TestFlight で実機インストールした場合はapi.push.apple.com
になるかと思います。
- 参考:
APNs への接続確認
以下のコマンドを実行し、APNs へ問題なく接続できることを確認します。
1openssl s_client -connect "${APNS_HOST_NAME}":443
特にエラーなど表示されず、Verify return code: 0 (ok)
などが表示されれば OK です。
認証トークンを生成
1 時間以内に生成したトークンで通知送信する必要があるため、送信の直前にトークンを生成します。
重要
フィールドの値が iat1 時間以上経過している場合、APNs はトークンを含むすべての通知を拒否し、エラーを返します。ExpiredProviderToken (403)
https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns#Create-and-encrypt-your-JSON-token
以下のコマンドを実行して認証トークンを生成し、環境変数へ設定します。
1JWT_ISSUE_TIME=$(date +%s) \
2JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =) \
3JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =) \
4JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}" \
5JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =) \
6AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
Push 通知の送信
APNs の接続確認と認証トークンの生成ができたら、以下のコマンドで Push 通知を送信します。
1curl -v \
2--header "apns-topic: $TOPIC" \
3--header "apns-push-type: alert" \
4--header "authorization: bearer $AUTHENTICATION_TOKEN" \
5--data '{"aps":{"alert":"test"}}' \
6--http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
特に問題なければ HTTP ステータス 200 で通知が送信されます。
Push 通知の送信時エラー
Push 通知送信コマンドを実行した際に、{"reason":"BadDeviceToken"}
というエラーが返され、通知の送信に失敗することがありました。
私の場合、entitlements ファイルの aps-environment を production に設定し、Xcode で Release ビルドを行った上で、APNs の本番サーバー(https://api.push.apple.com) に向けて通知を送信したところ、このエラーが発生しました。
しかし、同じビルドで APNs の開発サーバー(https://api.sandbox.push.apple.com) を使用して送信した場合は、正常に通知が届きました。
このことから、Xcode でインストールしたアプリは、たとえ Release ビルドであっても「APNs 開発環境向けのトークン」が発行されるのではないかと思います。
また、Xcode の「Automatically manage signing」を使用していた点も影響しているかもしれません。
なお、TestFlight 経由でインストールしたアプリでは、APNs 本番サーバーへの Push 通知が正常に動作しました。
おわりに
本記事では、コマンドを使って Push 通知を送信する方法についてまとめました。
これまでは、外部の Push 通知配信サービスやローカル Push 通知を利用して検証を行っていましたが、コマンドから直接通知を送信することで、ペイロードの内容などを柔軟にカスタマイズできる点が非常に便利だと感じました。
検証時や開発中のデバッグ用途としても有用ですので、Push 通知の仕組みを深く理解したい方にとって、参考になれば幸いです。
最後までお読みいただき、ありがとうございました!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ

業務ではiOS開発に携わらせていただいています。 まだまだ分からないことだらけで、日々分からないことと戦いながら仕事をしている者です。 ブログ記事は暖かい目で見ていただけるとありがたいです。