Tutorials

iOS Push Notifications Tutorial - Direct registration

Ably can deliver native Push Notifications to iOS devices using Apple’s Push Notification service. Native Push Notifications, unlike our channel-based Pub/Sub messaging, do not require the device to maintain a connection to Ably, as the underlying platform or OS is responsible for maintaining its own battery efficient transport to receive Push Notifications. Therefore, native Push Notifications are commonly used to display visual notifications to users or launch a background process for an app in a battery efficient manner.

In this tutorial, we’ll see how to set up and send Push Notifications to your iOS app using the Ably’s Push Notification service.

Ably supports two types of client permissions for access to the Push Notification service:

  • push-subscribe: A client with this permission is a push target device, and it can manage the registration and subscription for itself. However, it won’t be able to manage push registrations or channel subscriptions for any other device.
  • push-admin: A client with this contain permission has full access to the Push Admin API and can manage registrations and subscriptions for all devices.

Read more about permissions (also called capabilities) in our API documentation.

To enable Push Notifications on your device, it must be registered with APNS first. This can be done in two ways; you can either have the device register itself directly with Ably or delegate the registration work to your app server, which would then register the device with Ably on its behalf. In this tutorial we’ll implement direct device registration. You can jump to another tutorial if you’d rather have a server register the device with APNS.

Step 1 – Set up a free account with Ably

In order to run these tutorials locally, you will need an Ably API key. If you are not already signed up, you should sign up now for a free Ably account. Once you have an Ably account:

  1. Log into your app dashboard
  2. Under “Your apps”, click on “Manage app” for any app you wish to use for this tutorial, or create a new one with the “Create New App” button
  3. Click on the “API Keys” tab
  4. Copy the secret “API Key” value from your Root key and store it so that you can use it later in this tutorial

    Copy API Key screenshot

Step 2 – Create P12 Certificate

a. Creating an App ID

Before you can configure your devices to receive Push Notifications, you must first enable push in your app by either editing your current iOS App ID or creating a new one. This step will show you how to create a new App ID in your Apple developer portal with the correct certificates and services enabled.

Login to your Apple developer account and click on “Certificates, Identifiers and Profiles”. Now, select “App IDs” tab from side menu and create a new App ID using the plus button, as shown below.

Note: If you have already created an App ID for your app, you can skip this step. Instead you can simply edit your current App ID and enable Push Notifications.


Apple Add App ID

Next, specify the name of your app and Bundle ID (yours will have to be different but it should look something like the following)


Apple Add App Name & Description

Now, select Push Notifications from the list of app services and click on “Continue”.


Apple Add App ID & Description

Finally click on “Register”, then “Done” to complete the app registration step.

b. Generating a certificate from Keychain access

Launch the Keychain Access application in your Mac OS X and select:

Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority

After you reach the screen shown below, enter your email address and check the “Saved to disk” option. Click continue and select a location on your drive to save the certificate to.


Mac request signing certificate

c. Generating a development certificate

To generate a development certificate, follow the steps below:

  • Return to your Apple developer account.
  • Select your newly created App ID from the “App ID’s” list and click on “Edit”.
  • Scroll down to the Push Notifications and click on “Create Certificate”.


Click create ios push development cert

On the next screen, click continue as you have already completed that step. Now you’ll need to upload the signing certificate that we earlier saved to our disk. After that’s done, click on “Continue”.


Upload signing certificate

This will create a new downloadable Push certificate with a validity of one year. Go ahead and download it.


Download ios push cert

d. Generating the APNS P12

Double click on your newly downloaded certificate to add it to your Keychan access. Open the Keychain access application and right click on the certificate with the bundle ID we defined earlier, then select the export option from the drop-down menu.


Export development cert from keychain

Give your new certificate a name and make sure that the “.p12” option is selected and click save. On the next screen you can setup a password for this certificate. You may also be asked to supply your computer password.


P12 Password Export

Step 3 – Set up Apple Push Notifications

Before you can configure your devices to receive Push Notifications, you must first enable push in your Ably app by adding the APNS push service credentials and/or certificates to your app push dashboard. These credentials are then used by Ably to authenticate with APNS and deliver the notifications intended for a device. In this step we’ll setup the Ably dashboard with the correct certificates.

Navigate to your Ably dashboard. After selecting the correct environment(production or sandbox), click on the “Settings” tab. Now you can either import the “.p12” certificate, or create a PEM file and copy this into your dashboard.

a. Importing the .p12 file

Navigate to the “Notifications” tab on your app dashboard, then scroll down to the “Setting up Apple Push Notification Service” section.

Select the “.p12” file you had exported in the previous steps and enter the password that you had assigned earlier. This is shown below.


Apple Push P12

Click on “Save APNs settings” and you should get a confirmation of the certificate being imported successfully. You can additionally confirm that the file has been imported correctly by refreshing the page and seeing if the PEM cert and private key text boxes are now populated with the imported key details.

b. Creating a PEM file from the .p12 file

You can use openSSL, you can convert the recently imported “.p12” file to a PEM file with the following command:

openssl pkcs12 -in ./YOUR_CERT_NAME.p12 -out ./YOUR_CERT_NAME.pem -nodes -clcerts

Now, open the PEM file in your text editor and copy everything between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- (inclusive), into the “PEM cert” text box.

Next, copy everything between -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- (inclusive), into the “PEM private key” text box.

Your configure page should now look as shown below:


Apple Push PEM

Click on the “Save APNS settings” button to save your changes.

c. Enabling Push

By default the “Push Notifications” feature is disabled. So you will have to enable it explicitly.

Under “Channel rules” on your Ably app dashboard, add a new channel namespace (or edit an existing one) and check the “Persisted” and “Push Notifications enabled” checkboxes and save it. Keep in mind that this will be the same namespace you’ll later use in your iOS application. You can learn more about channel namespaces- from one of our support articles. We’ll use push as our channel namespace for this tutorial.

Channel namespaces allow you to logically group the different channels that are part of your application. You can further have various channels within a single namespace by separating the channel name from it’s namespace by a colon. For instance, a channel name in our Push Notifications app can be “push:my_channel_name”.

iOS Push Enabled

Step 4 – Add push to your application

In this step, we’ll see how you can enable the correct capabilities required to send Push Notifications to your iOS app.

If you are building a new iOS application, make sure the bundle ID of your app matches with the one that we created in the previous steps. We will be using cocoapods. You can follow the installation instructions from the cocoapods homepage.

Open your Podfile and add the following as a dependency:

use_frameworks!
pod 'Ably', '~> 1.1.3'

In your terminal run the following command to install the pod:

pod install

From this point on, we’ll be using the Xcode workspace. Open your target settings and go to the capabilities tab. Scroll down and turn on the Push Notifications capability as shown below.


iOS Push Capabilities

Now open your AppDelegate.swift file and paste the following code before your class declaration:

import UIKit
import Ably
import UserNotifications

let authURL = "YOUR_NGROK_HTTPS/auth"

Here we have set up an endpoint to the authorization server. You’ll need to replace this with your own ngrok provided URL after you have your server up and running. We have also imported the required modules.

Next, we’ll register with Ably as follows:

var realtime: ARTRealtime!
var channel: ARTRealtimeChannel!
var subscribed = false

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    DispatchQueue.main.async() {
        print("** didRegisterForRemoteNotificationsWithDeviceToken")
        ARTPush.didRegisterForRemoteNotifications(withDeviceToken: deviceToken, realtime: self.getAblyRealtime())
    }
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    DispatchQueue.main.async() {
        print("** didFailToRegisterForRemoteNotificationsWithError")
        ARTPush.didFailToRegisterForRemoteNotificationsWithError(error, realtime: self.getAblyRealtime())
    }
}

The code above contains the standard application handlers that allow you to register for Push Notifications. As you can see, we also notify the Ably platform if the registration was successful or not, so that it can handle registration for us.

Now, we need to authenticate the app with our auth server next, this will allow us to register as a push-admin. In the same file add the following code:

private func getAblyRealtime() -> ARTRealtime {
    let options = ARTClientOptions()
    options.authCallback = { params, callback in
        self.getTokenRequest() { json, error in
            do {
                callback(try ARTTokenRequest.fromJson(json!), nil)
            } catch let error as NSError {
                callback(nil, error)
            }
        }
    }
    realtime = ARTRealtime(options: options)
    realtime.connection.on { state in
        if let state = state {
            switch state.current {
            case .connected:
                print("connected")
            case .failed:
                print("failed")
            default:
                break
            }
        }
    }
    return realtime
}

func getTokenRequest(completion: @escaping (NSDictionary?, Error?) -> ())  {
    let requestURL = URL(string: authURL)!
    let urlRequest = URLRequest(url: requestURL as URL)
    let session = URLSession.shared
    let task = session.dataTask(with: urlRequest) {
        (data, response, error) -> Void in
        let httpResponse = response as! HTTPURLResponse
        let statusCode = httpResponse.statusCode
        if (statusCode == 200) {
            do{
                let json = try JSONSerialization
                    .jsonObject(with: data!, options:.allowFragments) as! NSDictionary
                completion(json, nil)
            } catch {
                print("There was an error while obtaining JSON")
            }
        }
    }
    task.resume()
}

In the first function, we have setup our connection to Ably. You can use your API Key here to authenticate with Ably, however, we’ve used the Token Authentication strategy as it’s more secure. For this, we’ve request a token from the auth endpoint of our server. The second function is where the bulk of the token request to our server happens. It’s a simple GET request and JSONSerialization.

Let’s add some error handling to the same class as shown below:

func didDeactivateAblyPush(_ error: ARTErrorInfo?) {
    if let error = error {
        // Handle error
        print("** push de-activation failed", error)
        return
    }
    print("** push de-activated, re-activating")
    self.realtime.push.activate()
}

The callback in the above function tells us if we have deactivated push. As there is no reason for us to do that at the moment, we simply reactivate it in such a scenario.

Next, we’ll add a delegate method that tells us if push is activated giving us an opportunity to subscribe to the channels. We will also handle the subscription here.

func didActivateAblyPush(_ error: ARTErrorInfo?) {
    if let error = error {
        // Handle error
        print("** push activation failed, err=\(String(describing: error))")
        return
    }
    print("** push activated")

    self.channel = self.realtime.channels.get("push")
    // Attach to channel, then subscribe device, then broadcast a push which we expect to eventually receive back.
    self.channel.attach() { (err) in
        print("** channel attached, err=\(String(describing: err))")

        self.channel.push.subscribeDevice { (err) in
            DispatchQueue.main.async {
                print("** device ID \(self.realtime.device.id)")
            }
            print("** channel.push.subscribeDevice: err=\(String(describing: err))")
            self.subscribed = true
        }
    }
}

This function is a delegate method, it tells us we have activated push and gives us an opportunity to subscribe to channels. We also print the device Id for debugging purposes. We can use this later when testing notifications.

Let us now add the callback for handling a notification. We’re not going to do anything special other than print it to the console.

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
    print("** received notification: \(userInfo)")
}

If you have the app running with the debugger attached, you would see the printed message when a notification is received. We don’t need to do anything else as the operating system will handle the generic messages we see on the lock screen. However, if the app is in the foreground you may want to use this method to show an alert to the user.

We need to register the user now. Paste the code below inside the didFinishLaunchingWithOptions() method where we request notifications from the user if possible. We setup our connection to ably and also reactivate AblyPush depending on our connection to Ably.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    print("** hello")

    UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]) { (granted, err) in
        DispatchQueue.main.async() {
            UIApplication.shared.registerForRemoteNotifications()
            print("** after registerForRemoteNotifications")
        }
    }

    self.realtime = self.getAblyRealtime()

    // Deactivating may be necessary as the deployed app might have state inconsistent with the Ably app
    // print("** De-activating Ably push")
    // self.realtime.push.deactivate()

    self.realtime.connection.on { (stateChange) in
        print("** connection state change: \(String(describing: stateChange))")
    }

    self.realtime.connection.on(ARTRealtimeConnectionEvent.connected) { (stateChange) in
        print("** connected, resetting Ably push")
        self.realtime.push.deactivate()
    }

    return true
}

Finally, make the app delegate conform to ARTPushRegistererDelegate so that we can receive our callbacks.

class AppDelegate: UIResponder, UIApplicationDelegate, ARTPushRegistererDelegate {...}

See this step in GitHub.

Step 5 – Setup Node.js server for authentication

In this step, we’ll setup an authentication server that’ll authenticate the clients with Ably using the Token Authentication strategy.

When a client is authenticated and connected to Ably, they are considered to be an authenticated client. Whilst an authenticated client has a verifiable means to authenticate with Ably, they do not necessarily have an identity. When using the Token Authentication, you can have your sever assign the IDs to all the clients at the time of authentication. Unlike in Basic Authentication, you’ll not be using a direct API key on the client side. Instead, you’ll use an authUrl or authCallback to authenticate the clients. This is a recommended strategy to perform authentication on the client side as illustrated in our Best Practice Guide

We’ll use Node to implement the server. Install node.js if you do not have it by downloading it from here.

Next, create a folder and change to this directory. We’ll be using express.js in this tutorial. It’s a JavaScript framework that makes it easy to accept REST request and send responses back, without needing to worry about the protocol level details. More information on express.js can be found on the website.

Run the following command in terminal:

npm init

You will be prompted to provide some information, just hit return in order to accept the defaults and then run the following commands to install our dependencies:

npm install express --save
npm install ably --save

Create a file called ‘index.js’ and add the following code for authentication.

const express = require('express')
const Ably = require('Ably');
var realtime = new Ably.Realtime({key: "YOUR_API_KEY" });
const app = express()
const port = 3000
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

app.get('/auth', function (req, res) {
  var tokenParams = {
    'clientId': /* Assign a random id */
  }; 
  realtime.auth.createTokenRequest(tokenParams, function(err, tokenRequest) {
    if (err) {
      res.status(500).send('Error requesting token: ' + JSON.stringify(err));
    } else {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(tokenRequest));
    }
  });
});

You can return to your terminal and run your node server using the following command:

$ node index.js

If you navigate to http://localhost:3000/auth on your web browser, you should see the following response, indicating authentication was successful:

{
  "keyName": /* your key name */,
  "ttl": 3600000,
  "timestamp": 1473894038255,
  "capability": "{\"*\":[\"*\"]}",
  "nonce": "f6799a8e7fa6f77b8e1dac55314789b1",
  "mac": "35nifY9SRZ8KRDfKOPIS1qYWGP16r2lD59zJo9TH8pA="
}

The above is a trivial auth example as anybody accessing the auth endpoint is automatically given a token. If you would like more information on auth and on it’s implementation head over to the Token Authentication tutorial.

See this step in GitHub.

Step 6 – Use ngrok to make server public

We’ll need to install ngrok to expose our local server to the internet. Follow the instructions on their ngrok’s website to install it.

Once you have it installed run the following command in terminal:

ngrok http 3000

You may find the above command not work, depending upon where you have ngrok installed on your drive. In such a case, try the following command instead:

$ ./ngrok http 3000

If everything goes as expected, you should see the following (or similar) within terminal:


Ngrok running example

It means that ngrok has opened up your localhost port 3000 to the internet and provided you with both a HTTP and HTTPS URLs to reach it.

Note: From this point on, you should use the https URL provided by ngrok and replace any existing or future http://localhost:3000 with that.

Step 7 – Testing Push

Now that our app is ready, lets test it out. Before using any of these methods make sure you have built and run the app on a real iOS device and accepted push notifications.

a. Testing using Device ID

Add the following method to your index.js and replace “YOUR_DEVICE_ID” with the id you just retrieved from your console:

app.get('/push/device', function (req, res) {
  var recipient = {
    deviceId: 'YOUR_DEVICE_ID'
  };
  var notification = {
    notification: {
      title: 'Hello from Ably!'
    }
  };
  realtime.push.publish(recipient, notification, function(err) {
    if (err) {
      console.log('Unable to publish push notification; err = ' + err.message);
      return;
    }
    console.log('Push notification published');
    res.send("Push Sent");
  });
})

Restart the server and navigate to YOUR-SERVER-BASE-URL/push/device in your browser. You should be able to receive a notification on your device as shown the image below.


Apple Push With Title

b. Testing using Client ID

We can test sending push notifications using the client ID, this allows us to push all devices with that client ID by adding the following code to your index.js file:

app.get('/push/client', function (req, res) {
  var recipient = {
    clientId: 'iosPushClient'
  };
  var notification = {
    notification: {
      title: 'Hello from Ably!'
    }
  };
  realtime.push.publish(recipient, notification, function(err) {
    if (err) {
      console.log('Unable to publish push notification; err = ' + err.message);
      return;
    }
    console.log('Push notification published');
    res.send("Push Sent");
  });
})

Note: If your clientId is different you will also need to change this.

Restart the server and go to YOUR-SERVER-BASE-URL/push/client you should receive a notification on your device as shown in the image below.


Apple Push With Title

c. Testing using Channels

Channel based Push Notifications allow us to push messages to a channel that the client on the device has subscribed to. To test this, add the following code to our index.js:

app.get('/push/channel', function (req, res) {
  var extras = {
    push: {
      notification: {
        title: 'Hello from Ably!',
        body: 'Example push notification from Ably.'
      },
      data: {
        foo: 'bar',
        baz: 'qux'
      }
    }
  };

  var channel = realtime.channels.get('push');
  channel.publish({ name: 'example', data: 'data', extras: extras }, function(err) {
    if (err) {
      console.log('Unable to publish message with push notification; err = ' + err.message);
      return;
    }
    console.log('Message with push notification published');
    res.send("Push Sent");
  });
})

Restart the server and go to YOUR-SERVER-BASE-URL/push/channel, you should receive a notification on your device. Notice how this notification has both a title and a body. You can do this on all three types of push notifications mentioned earlier, if you wish. Simply add a “body” ‘key/value’ pair to your notification part of the payload. See the above code for an example of this.


Apple Push With Title and message

See this step in GitHub.

Further Reading

  • Find the complete source code for this project on GitHub.
  • You may also wish to deregister devices or get all of the stats related to your application implementing Push Notifications, such as the number and type of device registrations, etc. More information on how to do this can be found in the Push Admin API docs.

Ready to get started?

Our free plan includes 3m messages per month, 100 peak connections, 100 peak channels, and loads of features.