When a user create a subscription through the PushManager.subscribe(), given the applicationServerKey
(VAPID public key), it creates a valid subscription the contents of which can be sent and stored in the database. The content sent to the database looks something like the following:
{"endpoint":":APA91bHqjZxM0VImWWqDRN7U0a3AycjUf4O-byuxb_wJsKRaKvV_iKw56s16ekq6FUqoCF7k2nICUpd8fHPxVTgqLunFeVeB9lLCQZyohyAztTH8ZQL9WCxKpA6dvTG_TUIhQUFq_n",
"keys": {
"p256dh":"BLQELIDm-6b9Bl07YrEuXJ4BL_YBVQ0dvt9NQGGJxIQidJWHPNa9YrouvcQ9d7_MqzvGS9Alz60SZNCG3qfpk=",
"auth":"4vQK-SvRAN5eo-8ASlrwA=="
}
}
Source: Introduction to push notifications
This object provides the unique endpoint in which the back-end server knows where to call in order to send the push notification to and does so authenticating with the corresponding VAPID private key.
Google states that the unique identifier for this endpoint is opaque and no personal data can be determined from it.
The identifier is opaque. As a developer, you can't determine any personal data from it. Also, it is not stable, so it can't be used to track users.
Following this tutorial from Surya Sankar, the entire subscription object can be sent to the server and stored within the database for use later when sending push notifications to the associated browser/device. However, before recording the entry into the database, it pares to see if an entry with the same contents have already been recorded as to not create duplicates.
@app.route("/api/push-subscriptions", methods=["POST"])
def create_push_subscription():
json_data = request.get_json()
subscription = PushSubscription.query.filter_by(
subscription_json=json_data['subscription_json']
).first()
if subscription is None:
subscription = PushSubscription(
subscription_json=json_data['subscription_json']
)
db.session.add(subscription)
db.sessionmit()
return jsonify({
"status": "success"
})
Source: push_subscriptions_api.py
Since the entire subscription object is utilized with webpush
to trigger a push notification, I understand that the entire subscription object will need to be sent to the server.
from pywebpush import webpush, WebPushException
import json
from flask import current_app
def trigger_push_notification(push_subscription, title, body):
try:
response = webpush(
# Uses entire subscription object stored in database as parameter
subscription_info=json.loads(push_subscription.subscription_json),
data=json.dumps({"title": title, "body": body}),
vapid_private_key=current_app.config["VAPID_PRIVATE_KEY"],
vapid_claims={
"sub": "mailto:{}".format(
current_app.config["VAPID_CLAIM_EMAIL"])
}
)
return response.ok
except WebPushException as ex:
if ex.response and ex.response.json():
extra = ex.response.json()
print("Remote service replied with a {}:{}, {}",
extra.code,
extra.errno,
extra.message
)
return False
def trigger_push_notifications_for_subscriptions(subscriptions, title, body):
return [trigger_push_notification(subscription, title, body)
for subscription in subscriptions]
Source: webpush_handler.py
This follows the guidelines under the documentation for this library (pywebpush) which states to do the following under their Usage section in their README.md file.
With that, I am curious about what parts of the subscription object contain sensitive information (i.e. "auth"
)? The documentation for PushSubscription.getKey()
states that "auth"
is an authentication secret and "p256dh"
is a public key. Given that push notifications can only be used over HTTPS, the munication between the client and server would be secure.
When trying to reference this subscription from the front-end, what part of this subscription object can I use as an identifier to reference this entry within the database?
(i.e. A GET
request to this endpoint would look like the following https:/example/client/push-subscription/<subscription-object-identifier>
)
I do not think I can use the entire subscription object as an identifier for this endpoint as it would expose the content under "auth"
. Could I use the content under "endpoint"
or "p256dh"
as the identifier as they would be unique and would not expose any sensitive data?
When a user create a subscription through the PushManager.subscribe(), given the applicationServerKey
(VAPID public key), it creates a valid subscription the contents of which can be sent and stored in the database. The content sent to the database looks something like the following:
{"endpoint":"https://fcm.googleapis./fcm/send/dpH5lCsTSSM:APA91bHqjZxM0VImWWqDRN7U0a3AycjUf4O-byuxb_wJsKRaKvV_iKw56s16ekq6FUqoCF7k2nICUpd8fHPxVTgqLunFeVeB9lLCQZyohyAztTH8ZQL9WCxKpA6dvTG_TUIhQUFq_n",
"keys": {
"p256dh":"BLQELIDm-6b9Bl07YrEuXJ4BL_YBVQ0dvt9NQGGJxIQidJWHPNa9YrouvcQ9d7_MqzvGS9Alz60SZNCG3qfpk=",
"auth":"4vQK-SvRAN5eo-8ASlrwA=="
}
}
Source: Introduction to push notifications
This object provides the unique endpoint in which the back-end server knows where to call in order to send the push notification to and does so authenticating with the corresponding VAPID private key.
Google states that the unique identifier for this endpoint is opaque and no personal data can be determined from it.
The identifier is opaque. As a developer, you can't determine any personal data from it. Also, it is not stable, so it can't be used to track users.
Following this tutorial from Surya Sankar, the entire subscription object can be sent to the server and stored within the database for use later when sending push notifications to the associated browser/device. However, before recording the entry into the database, it pares to see if an entry with the same contents have already been recorded as to not create duplicates.
@app.route("/api/push-subscriptions", methods=["POST"])
def create_push_subscription():
json_data = request.get_json()
subscription = PushSubscription.query.filter_by(
subscription_json=json_data['subscription_json']
).first()
if subscription is None:
subscription = PushSubscription(
subscription_json=json_data['subscription_json']
)
db.session.add(subscription)
db.session.mit()
return jsonify({
"status": "success"
})
Source: push_subscriptions_api.py
Since the entire subscription object is utilized with webpush
to trigger a push notification, I understand that the entire subscription object will need to be sent to the server.
from pywebpush import webpush, WebPushException
import json
from flask import current_app
def trigger_push_notification(push_subscription, title, body):
try:
response = webpush(
# Uses entire subscription object stored in database as parameter
subscription_info=json.loads(push_subscription.subscription_json),
data=json.dumps({"title": title, "body": body}),
vapid_private_key=current_app.config["VAPID_PRIVATE_KEY"],
vapid_claims={
"sub": "mailto:{}".format(
current_app.config["VAPID_CLAIM_EMAIL"])
}
)
return response.ok
except WebPushException as ex:
if ex.response and ex.response.json():
extra = ex.response.json()
print("Remote service replied with a {}:{}, {}",
extra.code,
extra.errno,
extra.message
)
return False
def trigger_push_notifications_for_subscriptions(subscriptions, title, body):
return [trigger_push_notification(subscription, title, body)
for subscription in subscriptions]
Source: webpush_handler.py
This follows the guidelines under the documentation for this library (pywebpush) which states to do the following under their Usage section in their README.md file.
With that, I am curious about what parts of the subscription object contain sensitive information (i.e. "auth"
)? The documentation for PushSubscription.getKey()
states that "auth"
is an authentication secret and "p256dh"
is a public key. Given that push notifications can only be used over HTTPS, the munication between the client and server would be secure.
When trying to reference this subscription from the front-end, what part of this subscription object can I use as an identifier to reference this entry within the database?
(i.e. A GET
request to this endpoint would look like the following https:/example./client/push-subscription/<subscription-object-identifier>
)
I do not think I can use the entire subscription object as an identifier for this endpoint as it would expose the content under "auth"
. Could I use the content under "endpoint"
or "p256dh"
as the identifier as they would be unique and would not expose any sensitive data?
1 Answer
Reset to default 15You can safely use the endpoint
as a unique identifier in your database and in the frontend calls to the backend.
We have been using it in the Pushpad Javascript SDK since 2015 without any problem.
For example, you can use the endpoint as an identifier when you make a call from Javascript to the backend in order to store data for that specific subscription.
An endpoint can be considered unique and secret (since it contains a long random token).
This is true now and it was also true at the beginning of the standard... in the past the keys
and payload were not even present and the endpoint
alone was used for everything, including fetching the new notifications from the application server.
For the keys
I would simply keep them secret in the database and not use them as identifiers.