Web push notification using Python

Building a web push should be straight forward. But while implementing it, I found the resources lacks and there is no straight forward guide to follow and implement it in your existing stack or form scratch. Which make sense as its still in early days. In this blog post, I would be covering step by step procedure to build a web push service. If you are implementing a push service from scratch or integrating it in your existing application, this blog post will help you to reach your goal. There will be following sections in this post:

  • How web push works
  • Building a push service
  • Browser supports
  • References

How web push works

On high level web push needs three parties/component to work. Those are:

  • Client side application: Get users permissions, get users subscription token and sends to the backend service.
  • Push Service: Validates push request coming from backend service and forward the push message to the appropriate browser.
  • Backend service: Persists users subscription information and initiate push sending.

Steps to send/receive push web push notification

  1. User accepts push permission and browser generate push subscription token via communicating with the Push API
  2.  Client app should send the subscription information to the backend service and backend service should be persisting the subscription information and use it to the next steps
  3. Backend push service initiate the push and send the payload to the specific push service (which is denoted in the users subscription information)
  4. Push service receives the push notification and forward it the specific user and browser display the notification

Backend service using python

We will be building a REST interface which will communicate with the client application and push service. It will store the subscription information of users and distribute VAPID  public key. VAPID is the short term for Voluntary Application Server Identification, the generated public key will be used via the client app.  We will need to develop following API endpoints:

  1. /subscription
    • GET – to get vapid public key
    • POST – to store subscription information
  2. /push
    • POST – will send push request to all users ( will be used for testing )

Generate the VAPIDs via following command:


openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem
openssl ec -in vapid_private.pem -pubout -out vapid_public.pem

Create base64 encoded DER representation of the keys:


openssl ec -in ~/.ssh/vapid_private.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txt

openssl ec -in ~/.ssh/vapid_private.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt

These VAPIDs keys will be used in the newly developed backend service. We will be using pywebpush library for sending the web push notification. We will be wrapping the push like below by using newly generated keys:

import os
from pywebpush import webpush, WebPushException

DER_BASE64_ENCODED_PRIVATE_KEY_FILE_PATH = os.getenv("DER_BASE64_ENCODED_PRIVATE_KEY_FILE_PATH")
DER_BASE64_ENCODED_PUBLIC_KEY_FILE_PATH = os.getenv("DER_BASE64_ENCODED_PUBLIC_KEY_FILE_PATH")

VAPID_PRIVATE_KEY = open(DER_BASE64_ENCODED_PRIVATE_KEY_FILE_PATH, "r+").readline().strip("\n")
VAPID_PUBLIC_KEY = open(DER_BASE64_ENCODED_PUBLIC_KEY_FILE_PATH, "r+").read().strip("\n")

VAPID_CLAIMS = {
"sub": "mailto:youremail"
}

def send_web_push(subscription_information, message_body):
    return webpush(
        subscription_info=subscription_information,
        data=message_body,
        vapid_private_key=VAPID_PRIVATE_KEY,
        vapid_claims=VAPID_CLAIMS
    )

Full service code can be found here as gist. Follow the gist readme for details about running the service.

Frontend application to test the backend service

Rather than writting an application form scratch lets use google push example client app. Which you will find here.
Use the 08-push-subscription-change version which is last part of step by step tutorial from Google. Put the VAPID public key in main.js in this variable applicationServerPublicKey. Client side application will use the public key to generate the subscription information. And send the public key which will be used by Push Service.

Putting it all together

Meanwhile pull the whole code from gist, install necessary packages and run it via following commands.


pip install -r requirements.txt

python api.py

Run following command to get the VAPID public key from the service via following command:


curl -X GET http://127.0.0.1:5000/subscription

It will return the public key like as key value pair. Copy the public key and paste it to frontend application as value of applicationServerPublicKey in main.js. 

Navigate the browser to the Push lab application and click on “Enable Push Messaging”, a browser pop up will appear like below:

Click on “Allow” by which you will be giving permission to the application to show web push notification. And the client app will generate the PushSubscription object. Which we will need to send to the our backend service which will persists the information and use to send around a push notification.

It will generate the payload which should be sent to backend service via following curl request:

curl -X POST http://127.0.0.1:5000/push

The push will arrive right top of the screen something like below:

Browser supports push

While writing this blog post Chrome and Firefox only supports push. Here you can find latest supported browser lists.

Miscellaneous

On Mac while developing the backend service, openssl can throw exception while sending out push, cryptography library can not find appropriate version of openssl. Which looks like below:

Symbol not found: _EC_curve_nist2nid
Referenced from: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
Expected in: /usr/lib/libcrypto.dylib in /usr/local/opt/openssl/lib/libssl.1.0.0.dylib

To fix the issue we need to export openssl library path like below:


export DYLD_LIBRARY_PATH=/usr/local/opt/openssl/lib

Also I faced an issue with python cryptography library it can not find the right version of openssl and install cryptography based on inappropriate version.
To overcome that, I had to uninstall and install it again like below:

pip uninstall cryptography
LDFLAGS="-L/usr/local/opt/openssl/lib" pip install cryptography --no-use-wheel

References

  1. Google post details about how push works
  2. Firefox post web push API 
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s