Update Dec 5, 2016: I've packaged a library for interacting with APNs using the methods outlined below which is available via pip. https://github.com/genesluder/python-apns
No more fiddling with push notification certificates! At last, Apple offers token-based authentication with the Apple Push Notification Service, greatly simplifying push server maintenance.
You simply generate a key once in the member center and use that key to generate authentication tokens on your server. You never need to recreate the key unless you chose to- for instance if you ever have reason to believe your key has been compromised.
Below I'll walk though how to generate an authentication token and send a push notification to the HTTP/2 service in Python. A full example is included at the end.
APNS uses JSON web tokens, so grab the Python library, PyJWT
pip install pyjwt
You'll also need the optional PyJWT dependency cryptography to support ECDSA algorithms
pip install cryptography
We need to use the HTTP/2 protocol, so grab Hyper
pip install hyper
Now, head over to the Apple member center and generate an APNS auth key. In the Certificates, Identifiers & Profiles section of the Member Center, under Certificates there is a new section APNs Auth Key. There click the add button to create a new key.
Under Production select Apple Push Notification Authentication Key (Sandbox & Production) and click continue and a key will be created for you.
Download the .p8 file and note the key ID and the .p8 filename, as we'll need those in a moment.
APNS_KEY_ID = 'ABC123DEFG' APNS_AUTH_KEY = '/PATH_TO/APNSAuthKey_ ABC123DEFG.p8'
While you're in the member center, grab your Team ID as well in the membership area.
TEAM_ID = 'DEF123GHIJ'
APNS wants the JSON web token in the format:
Header { "alg": "ES256", "kid": "ABC123DEFG" } Claim { "iss": "DEF123GHIJ", "iat": 1437179036 } SECRET
Using the information we collected above, we now have everything we need to generate our token.
import jwt import time from hyper import HTTPConnection ALGORITHM = 'ES256' APNS_KEY_ID = 'ABC123DEFG' APNS_AUTH_KEY = '/PATH_TO/APNSAuthKey_ ABC123DEFG.p8' TEAM_ID = 'DEF123GHIJ' f = open(APNS_AUTH_KEY) secret = f.read() token = jwt.encode( { 'iss': TEAM_ID, 'iat': time.time() }, secret, algorithm= ALGORITHM, headers={ 'alg': ALGORITHM, 'kid': APNS_KEY_ID, } )
Now that we have our token, we can use it to send a push notification. Apple specifies the following format for a push request:
HEADERS - END_STREAM + END_HEADERS :method = POST :scheme = https :path = /3/device/DEVICE_REGISTRATION_ID host = api.development.push.apple.com authorization = bearer AUTH_TOKEN apns-id = OPTIONAL_UUID apns-expiration = 0 apns-priority = 10 apns-topic = TOPIC DATA + END_STREAM { "aps" : { "alert" : "Hello" } }
A few things to note:
And now we have everything we need to send a push request:
import json BUNDLE_ID = 'com.example.CheerfulBirds' REGISTRATION_ID = '00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0' path = '/3/device/{0}'.format(REGISTRATION_ID) request_headers = { 'apns-expiration': '0', 'apns-priority': '10', 'apns-topic': BUNDLE_ID, 'authorization': 'bearer {0}'.format(token.decode('ascii')) } payload_data = { 'aps': { 'alert' : 'Oh hai!' } } payload = json.dumps(payload_data).encode('utf-8') conn = HTTPConnection('api.development.push.apple.com:443') conn.request( 'POST', path, payload, headers=request_headers ) resp = conn.get_response() print(resp.status) print(resp.read())
If the server returns a 200 status with an empty body, success!! Otherwise, if the error message isn't self explanatory, check the table here
That's it!
import json import jwt import time from hyper import HTTPConnection ALGORITHM = 'ES256' APNS_KEY_ID = 'ABC123DEFG' APNS_AUTH_KEY = '/PATH_TO/APNSAuthKey_ ABC123DEFG.p8' TEAM_ID = 'DEF123GHIJ' BUNDLE_ID = 'com.example.CheerfulBirds' REGISTRATION_ID = '00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0' f = open(APNS_AUTH_KEY) secret = f.read() token = jwt.encode( { 'iss': TEAM_ID, 'iat': time.time() }, secret, algorithm= ALGORITHM, headers={ 'alg': ALGORITHM, 'kid': APNS_KEY_ID, } ) path = '/3/device/{0}'.format(REGISTRATION_ID) request_headers = { 'apns-expiration': '0', 'apns-priority': '10', 'apns-topic': BUNDLE_ID, 'authorization': 'bearer {0}'.format(token.decode('ascii')) } # Open a connection the APNS server conn = HTTPConnection('api.development.push.apple.com:443') payload_data = { 'aps': { 'alert' : 'All your base are belong to us.' } } payload = json.dumps(payload_data).encode('utf-8') # Send our request conn.request( 'POST', path, payload, headers=request_headers ) resp = conn.get_response() print(resp.status) print(resp.read()) # If we are sending multiple requests, use the same connection payload_data = { 'aps': { 'alert' : 'You have no chance to survive. Make your time.' } } payload = json.dumps(payload_data).encode('utf-8') conn.request( 'POST', path, payload, headers=request_headers ) resp = conn.get_response() print(resp.status) print(resp.read())