How to send APNs push messages using APNs Auth Key and standard CLI tools?
Asked Answered
N

4

43

Apple recently added a new authentication method to APNS ( Apple Push Notification Authentication Key (Sandbox & Production)).

enter image description here

The downloaded key is a .p8 file with a private key:

$ cat APNSAuthKey_3HHEB343FX.p8
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBH...Already.Revoked...lHEjCX1v51W
-----END PRIVATE KEY-----

I am using APNs messages using the old method - adding them to keychain, asking for a certificate and using OpenSSL to send messages to gateway.production.push.apple.com:2195.

How do I send push notifications using standard CLI Linux tools (OpenSSL, Python etc.) using the new format?

Nigrify answered 9/10, 2016 at 12:36 Comment(0)
C
74

If you have curl with HTTP/2 support and openssl with ECDSA support installed on your machine, you can use the following script to test push notifications using an APNs Auth Key:

#!/bin/bash

deviceToken=b27371497b85611baf9052b4ccfb9641ab7fea1d01c91732149c99cc3ed9342f

authKey="./APNSAuthKey_ABC1234DEF.p8"
authKeyId=ABC1234DEF
teamId=TEAM123456
bundleId=com.example.myapp
endpoint=https://api.development.push.apple.com

read -r -d '' payload <<-'EOF'
{
   "aps": {
      "badge": 2,
      "category": "mycategory",
      "alert": {
         "title": "my title",
         "subtitle": "my subtitle",
         "body": "my body text message"
      }
   },
   "custom": {
      "mykey": "myvalue"
   }
}
EOF

# --------------------------------------------------------------------------

base64() {
   openssl base64 -e -A | tr -- '+/' '-_' | tr -d =
}

sign() {
   printf "$1" | openssl dgst -binary -sha256 -sign "$authKey" | base64
}

time=$(date +%s)
header=$(printf '{ "alg": "ES256", "kid": "%s" }' "$authKeyId" | base64)
claims=$(printf '{ "iss": "%s", "iat": %d }' "$teamId" "$time" | base64)
jwt="$header.$claims.$(sign $header.$claims)"

curl --verbose \
   --header "content-type: application/json" \
   --header "authorization: bearer $jwt" \
   --header "apns-topic: $bundleId" \
   --data "$payload" \
   $endpoint/3/device/$deviceToken

NOTE: I use a slight variation of this script for testing on macOS with homebrew versions of curl and openssl: http://thrysoee.dk/apns/

Apple now have documentation for this method to Send a Push Notification Using a Token.

Cinematograph answered 19/10, 2016 at 20:13 Comment(9)
@JessThrysoee Your "slight variation" works like a CHAMP. Thanks for posting your hard work for all to see, it's people like you that keep the community juiced +1Tsan
Thank you for this very helpful script. On my Mac, I had to add the "--with-nghttp2" option to install curl with http2 support (as the home-brew curl doesn't currently seem to have the http2 support by default): brew install curl --with-nghttp2. (or as I already had curl installed: brew reinstall curl --with-nghttp2 ) Then on the curl command in the script, I added the option: --http2 and script works well. Thank you.Jenson
I keep getting "unable to load key file"Roar
As a note, curl must be compiled with OpenSSL and nghttp2 if using brew: brew install --with-openssl --with-nghttp2 curlPigeonhole
I am getting error when execute command sh apns.sh on terminalBiology
> Content-Length: 247 > * upload completely sent off: 247 out of 247 bytes Warning: Binary output can mess up your terminal. Use "--output -" to tell Warning: curl to output it to your terminal anyway, or consider "--output Warning: <FILE>" to save to a file. * Failed writing body (0 != 33) * Closing connection 0Biology
Homebrew recently removed install options for curl so install curl-openssl brew install curl-openssl and then in the script change curl curl=/usr/local/opt/curl-openssl/bin/curlOlcott
@Olcott I have updated the macOS script with your homebrew curl-openssl change. Thanks.Cinematograph
iOS 13, swift 5. Works perfectly!! THANKYOUMendelian
W
13

You can send push notification by NODE JS using Apple Push Notification Authentication Key (Sandbox & Production). There is a tutorial provided by Elad Nava in this link

This tutorial has all steps of creating the Apple Push Notification Authentication Key and setting up a local server to run Node JS code for sending push notification. You can run the code in your local machine and test the push notification.

Hope this will help.

Wyman answered 17/10, 2016 at 6:19 Comment(1)
This is life saver! It's so elegant!Lamed
D
6

Voilà in PHP what it looks like with curl and HTTP/2. This script return the 200 ok status code along with the generated token id.

// THE FINAL SCRIPT WITHOUT DEPENDENCIES!!! ...except curl with http2
$device_token = "a0abd886etc...";
//echo $key;
$kid      = "YOURKEYID";
$teamId   = "YOURTEAMID";
$app_bundle_id = "your.app.bundle";
$base_url = "https://api.development.push.apple.com";

$header = ["alg" => "ES256", "kid" => $kid];
$header = base64_encode(json_encode($header));

$claim = ["iss" => $teamId, "iat" => time()];
$claim = base64_encode(json_encode($claim));

$token = $header.".".$claim;
// key in same folder as the script
$filename = "KeyFromApple.p8";
$pkey     = openssl_pkey_get_private("file://{$filename}");
$signature;
openssl_sign($token, $signature, $pkey, 'sha256');
$sign = base64_encode($signature);

$jws = $token.".".$sign;

$message = '{"aps":{"alert":"You are welcome.","sound":"default"}}';

function sendHTTP2Push($curl, $base_url, $app_bundle_id, $message, $device_token, $jws) {

    $url = "{$base_url}/3/device/{$device_token}";
    // headers
    $headers = array(
        "apns-topic: {$app_bundle_id}",
        'Authorization: bearer ' . $jws
    );
    // other curl options
    curl_setopt_array($curl, array(
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
        CURLOPT_URL => $url,
        CURLOPT_PORT => 443,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POST => TRUE,
        CURLOPT_POSTFIELDS => $message,
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_SSL_VERIFYPEER => FALSE,
        CURLOPT_HEADER => 1
    ));
    // go...
    $result = curl_exec($curl);
    if ($result === FALSE) {
        throw new Exception("Curl failed: " .  curl_error($curl));
    }
    print_r($result."\n");
    // get response
    $status = curl_getinfo($curl);
    return $status;
}
// open connection
$curl = curl_init();
sendHTTP2Push($curl, $base_url, $app_bundle_id, $message, $device_token, $jws);
Damselfly answered 12/10, 2017 at 13:32 Comment(1)
you're welcome. I had a hard time make it works without dependencies.Damselfly
F
2

The answer provided by Nicolas Manzini, while helpful, did not fully work for me. I also wanted to use command line curl. In particular, reading in the .p8 file was causing some problems. A modified version:

<?php

$teamid = 'YOURTEAMID';
$keyid = 'A464FN6T93';     // in the name of the file downloaded from Apple Certs

// since it is a universal key, I wanted this in a location accessible to several accounts
$keyFile = '/path/to/file/AuthKey_A464FN6T93.p8';

$privateKey = openssl_pkey_get_private('file://' . $keyFile);

if (! $privateKey) {
    die('could not find: ' . $keyFile);
}

$header = ['alg' => 'ES256', 'kid' => $keyid];
$header = base64_encode(json_encode($header));

$claim = ['iss' => $teamid, 'iat' => time()];
$claim = base64_encode(json_encode($claim));

$tok = $header . '.' . $claim;

// pass in empty $signature, 2nd line below fills it
$signature = '';
$result = openssl_sign($tok, $signature, $privateKey, OPENSSL_ALGO_SHA256);      //  'sha256'
if (! $result) {
    die('unable to create signature');
}

$sign = base64_encode($signature);

openssl_free_key($privateKey);

$jwt = $tok . '.' . $sign;

foreach($tokens as $token) {
    $cmd = '\
    /usr/local/bin/curl -v \
    -d \'{"aps":{"alert":{"title":"Notification Title","body":"You are being notified!"},"sound":"default"}}\' \
    -H "apns-topic: com.app.bundle.id" \
    -H "authorization: bearer ' . $jwt . '" \
    -H "content-type:application/json" \
    --http2 \
    https://api.push.apple.com/3/device/' . $token . ' 2>&1';    // ending w/ 2>&1, sends output to $output

    exec($cmd, $output, $return);

    if ($return != 0) {
        // be notified of error
    }
    else {
        foreach($output as $line) {
            if (strpos($line, 'apns-id')) {
                $apns = $line;
            }
        }
    }
}

?>
Fishwife answered 3/7, 2019 at 13:33 Comment(1)
what if i want to send to multiple devices the same message, can i use an array of tokens?Cartography

© 2022 - 2024 — McMap. All rights reserved.