Safari Push Notifications: Complete Setup

The walkthrough that stops you pulling your hair out

safariPush.jpg

Safari Push Notifications is an Apple developer service that delivers notifications directly to your Mac. Beyond the web app notification use case, it is a useful tool for organisations to deliver notifications directly to their teams — without spending an additional $299 for an enterprise developer licence for in-house applications.

If you have backend web services, mission critial processes that can’t afford downtime, or real time market analysis being processed behind the scenes, Safari Push Notifications are very useful for keeping you in the loop without spamming email or developing custom apps just for status updates.

If you wish to integrate SPNs, an Apple Developer Licence is required, however this article also provides good insights for those contemplating setting up SPNs, or just for the curious wondering how they work.

Apple provide a comprehensive integration guide for implementing the service. However, for a variety of reasons — I suspect mostly because of the broad range of protocols used in the service — integrating can cause headaches.

Here are some pretty serious problems I have found integrating SPNs over the years:

  • Projects that have broken code, with no logging functionality
  • Permission problems on the server preventing log reporting
  • Push packages not being configured correctly (if you’re new to SPNs, we will go over what your push package consists of)
  • Apple miss vital configuration documentation, like being compulsory to match the amount of notification arguments with how many URL parameters you configured in your website.json file; if they are blank, an empty string is required.

When an error occurs and you do not have logging functioning, paranoia kicks in. You begin to wonder: Is my certificate valid? Is SELinux blocking the Safari Agent? Did I use the correct encryption function? Is this push package zipped up correctly?

This guide will hopefully give you confidence in your process management and maintain a coherent way of getting your service implemented. What will be covered in small coherent steps:

  • Generating your website push certificate.
    An Apple provided certificate from your developer portal is needed in order to send notifications.

  • Configuring your raw push package folder, and then using the companion PHP file (link also provided below) to zip up your final push package.
    Your “push package” is a folder of icons and configurations that is used by each Mac that subscribes to your notification service.

  • Configuring your web server to handle Safari Agent requests. I will be using NginX to route 2 requests that Safari Agent calls.
    When a client attemptes to subscribe to your service, a Safari Agent (from the client’s mac) will make a call to your server to fetch your push package. We need to handle these requests and present the package as a zip folder.

  • Running the front end Javascript to subscribe to your notifications, and getting the device token returned.
    Granting a client access to your notifications is very simple using Javascript. If a valid push package is returned, access will be granted and a device token is given in a JSON response. A device token is a unique identifier for each Mac that subscribes to the notification service. When sending a notification, the device token determines which Mac the notification will be sent to.

  • Obtaining the Apple WWDRCA certificate.
    Yes, another certificate is needed when actually sending notifications.

  • Configuring a PHP script to connect to Apple’s notification service gateway and send a notification.
    Here we will actually deliver a notification! The script provided here is written in PHP due to it being readily available on Github, but you are by no means limited to using PHP.

Let’s run through the process.

Generating your Website Push Certificate

SPNs require a certificate from Apple, generated from your developer account. Do the following in your Apple Developer Certificates section to generate your Push ID certificate.

  • Visit the Website Push IDs section under Certificates, Identifiers & Profiles

  • Click the + icon on the top right to add a new Website Push ID

  • Description: Can be your service name.

  • Identifier: typically web.com.domain.appname. If Medium were creating a notification service, they might use web.com.medium.stories for example. Click Continue, Register, and Done.

  • If you now go to Edit the record, we can now create a certificate for the Push service. Do that by clicking Create Certificate.

  • At this stage you need a CSR from your Keychain. On your Mac, open Keychain Access and from the top menu, go to Keychain Access, Certificate Assistant, Request a Certificate from a Certificate Authority.

  • Fill in your email address, and Common Name can be the identifier you used for your Push ID (web.com.domain.appname). Change Request Is to Save to Disk.

  • Continue and save the certificate, on your Desktop perhaps for easy access.

  • With this new file, go back to Apple Developer and upload the certificate where we last left off. Click Continue, then Download the certificate.

  • Now, back in Keychain Access, make sure the Login keychain is selected on the upper left menu, and My Certificates on the lower left menu. Now drag your newly downloaded certificate into that list.
    (Double clicking the certificate to open in Keychain may fail depending on what state the app is in)

  • Right click the certificate in Keychain, and Export it as a .p12. You will be prompted to input a password. Do so and remember it.

Uploading certificates to server and converting to .pem

We are almost done with the push certificates. Login to your server and create a directory dedicated to the push service. Lets say /var/www/safari.push/.

  • Create the directory: sudo mkdir /var/www/safari.push

  • Create a certs directory to store the certificates: sudo mkdir /var/www/safari.push/certs

  • Change user permissions to your user in order to upload the certificates: sudo chown -R your_user /var/www/safari.push/certs

  • Upload your certificate.cer file and certificate.p12 file, using SFTP or another secure protocol.

  • Now generate a .pem file from your .p12 file. To do this, run the following:
    cd /var/www/safari.push/certs
    openssl pkcs12 -in certificate.p12 -out certificate.pem -nodes

The Apple Worldwide Developer Relations Intermediate Certificate

The Apple WWDRCA certificate is also needed later on when sending notifications. Download it from here and also upload it to your certs directory.

Now with the certificates out of the way, let’s move on to the push package.

Configuring your raw push package

Your push package is a zip folder of files that are needed to get the notification service working. We set up a raw package consisting of icons and a website.json file, then run a PHP script to generate a couple more files.

All that is needed in your raw push package are your icons and website.json file. Apple document a signature and manifest.json file, but these are generated with the PHP Companion Script that follows this step.

This is why we are calling this folder your raw push package. The final push package will be a zip folder that the PHP script will generate.

Within your safari.push directory, create a pushPackage.raw folder and files within to match the following structure:

pushPackage.raw 
    website.json
    icon.iconset   
        icon_16x16@2x.png  
        icon_16x16.png  
        icon_32x32@2x.png  
        icon_32x32.png
        icon_128x128@2x.png  
        icon_128x128.png

icon.iconset

icon.iconset is a folder, containing your icons. These icons are displayed when notifications are delivered, as well as in Safari preferences, keep them consistent. Make the 2x images twice as big as their figures. e.g. 16x16@2x is actually a 32 x 32px image.

website.json

This is just a JSON formatted file with some details about your service. Let’s run through it.

{
    "websiteName": "Your Website Name",
    "websitePushID": "web.com.domain.appname",
    "allowedDomains": ["https://yourdomain.com"],
    "urlFormatString": "http://yourdomain.com/notification/%@/%@",
    "authenticationToken": "a_random_string_atleast_16_characters",
    "webServiceURL": "https://yourdomain.com/push"
}
  • websiteName: Can use your Website Push ID Description to keep things consistant.

  • websitePushId: Super important to be the same as your Apple Push ID.

  • allowedDomains: An array of top level domains you want whitelisted to grant notifications.

  • urlFormatString: The URL a user is taken to when they click a received notification. Note how many %@ there are at the end — this is how many arguments your URL supports, and must match the amount of arguments you place in your notification payload (more on the payload later).

  • authenticationToken: A random string at least 16 characters long.

  • webServiceUrl: The URL that handles Safari Agent requests when granting / removing subscribers, and writing logs. This URL must point to your /var/www/safari.push folder in this guide. We need to configure NginX to do that, in the next section.

Configure the PHP Companion File

The companion file will be the index file of your safari.push directory; its job is to generate a valid push package upon request. Lets go ahead and configure that file:

  • Download it from here and change the name to index.php. Upload it to your /var/www/safari.push directory.

The script is not complete. Make the following changes:

  • $certificate_password — change to the password you assigned earlier

  • $certificate_path — change to certs/certificate.p12

  • Locate the create_signature function, and within it the following like: openssl_pkcs7_sign(“$package_dir/manifest.json”, $signature_path, $cert_data, $private_key, array(), PKCS7_BINARY | PKCS7_DETACHED);

This line is incomplete, as the apple WWCRCA certificate also needs to be supplied as an additional argument. Change it to the following:

openssl_pkcs7_sign(“$package_dir/manifest.json”, $signature_path, $cert_data, $private_key, array(), PKCS7_BINARY | PKCS7_DETACHED,”/var/www/safari.push/certs/AppleWWDRCA.pem”);

Notice that at the end of the file, a push package is echoed as a zip folder. this triggers a download if you visit this page in your web browser.

Configuring NginX to handle Safari Agent Requests

Even though you are not limited to NginX, I will be using it here to demonstrate how to route the safari agent requests.

In this guide I will focus on 2 endpoints, downloading the push package and logging errors. These endpoints are webServiceURL/version/pushPackages/websitePushID and webServiceURL/version/log respectively.

Use the following locations configuration as a template for your own configuration:

#Safari Push Package Request
location /push/v2/pushPackages/web.com.domain.appname {
      add_header "Access-Control-Allow-Origin"  *;
      allow all;
      fastcgi_pass 127.0.0.1:9000;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME /var/www/safari.push/index.php;
      include fastcgi_params;
}

#Safari Push Log
location /push/v2/log {
     
      add_header "Access-Control-Allow-Origin"  *;
      allow all;
      fastcgi_pass 127.0.0.1:9000;
      fastcgi_index log.php;
      fastcgi_param SCRIPT_FILENAME /var/www/safari.push/log.php;
      include fastcgi_params;
}

There are some things to run through here:

  • Remember to change the web.com.pushid value to your Push ID.

  • Safari Agent may fall back to v1 instead of v2 in some cases, so check out your NginX /var/log/nginx/access.log and /var/log/nginx/error.log if things do not seem to be working.

  • The log endpoint points to the log.php file in your safari.push folder. The contents of this file just handles the error received, and stores it in a log file:

<?php
$file = 'log';
$current = file_get_contents($file);
$current .= file_get_contents("php://input") . "\n";
 
file_put_contents($file, $current);

You may wish to run a touch /var/www/safari.push/log command now to initiate that file.

At this point, visit the push package endpoint (https://yourdomain.com/v2/pushPackages/web.com.domain.appname) in your browser to check the package is being downloaded. If not, check the NginX access logs to troubleshoot the issue.

Run client Javascript to Grant Access / Retreive Device Token

It is time to put our push package and server setup to the test and request access to our notification service. This is quite easy to do —

  • Copy and paste Figure 2.3 from the Apple documentation into your front end page.

  • Find the permissionData.permission === 'granted' if statement and include a console.log(permissionData); in there.

Now, if everything is working well, you should be able to refresh the page and be greeted with an Allow or Deny notification dialogue prompt. Upon Allowing, refresh the page and check your Javascript Console. The device token should be waiting for you to copy, under permissionData.deviceToken.

In real world use, you should handle the response and display UI / store the device token in your database associated with a user. For this guide we are just obtaining the device token so we can then test notifications.

Sending Live Notification

You are now ready to send a live notification. We are going to refer to a PHP script that has been written to handle this function already.

Check out https://github.com/Gypsyan/APNS_Safari_Push, a ready-made PHP script for sending notifications. Well, almost. Let’s run down some things to look out for in this file:

  • $certificate needs to point to your .pem certificate.

  • $passphrase needs to be your certificate passphrase.

  • $token — the device token to send the notification to. This is not very flexible when you want the same notification to go to multiple users. For that, change this to an array of tokens and create a for loop from line 62–100.

  • Big Issue: The notification will fail if the arguments supplied do not match your website.json argument count ( remember the %@?). For this reason it may be worth testing $body with a simple JSON string before converting an array to JSON.

  • There is no debug control here, echos are everywhere. You may wish to create a DEBUG environment variable to remove all that feedback in production.

  • Connect a database to the script to make it smarter. You may also wish to $_POST the notification details, then verify them within your own systems.

Further Implementation

At this stage you have a working notification system. To further improve the system, you could do the following:

  • Configure an additional NginX location to handle an endpoint for firing a notification (using the safari_push.php script).

  • Configure an additional NginX location for routing the notifications, setting up the endpoint for your urlFormatString within website.json. Once a user clicks the notification, this file will be responsible for taking the user to a webpage or deep linking to somewhere else.

  • Better front end management. When do you want to flag the notification Allow / Deny dialogue? Where do you want to set up the unsubscribe functionality? These are all things to consider on top of a working fundamental system.

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now
Logo
Center