I cannot seem to get push notifications to work whatever I try…
This is the error code.
{"code":401,"errno":109,"error":"Unauthorized","message":"InvalidSignature","more_info":"http://autopush.readthedocs.io/en/latest/http.html#error-codes"}
It appears that the issue has something to do with ether a key mismatch or invalid signature.
Here are some of the resources I was using:
https://blog.mozilla.org/services/2016/08/23/sending-vapid-identified-webpush-notifications-via-mozillas-push-service/
https://autopush.readthedocs.io/en/latest/http.html#error-codes
https://datatracker.ietf.org/doc/rfc8292/
I’m generating the public/private keys as so:
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function generateVapidKeys(){
if(file_exists('vapid.json')){
$vapidKeys = json_decode(file_get_contents('vapid.json'));
return base64url_encode(hex2bin('04'.$vapidKeys->x.$vapidKeys->y));
}else{
$keyPair = openssl_pkey_new([
'digest_alg' => 'sha256',
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1', // P-256 curve
]);
}
$privateKeyDetails = openssl_pkey_get_details($keyPair);
$x = str_pad(bin2hex($privateKeyDetails['ec']['x']), 64, '0', STR_PAD_LEFT);
$y = str_pad(bin2hex($privateKeyDetails['ec']['y']), 64, '0', STR_PAD_LEFT);
$d = str_pad(bin2hex($privateKeyDetails['ec']['d']), 64, '0', STR_PAD_LEFT);
file_put_contents('vapid.json', json_encode([
'x' => $x,
'y' => $y,
'd' => $d,
], JSON_PRETTY_PRINT));
return base64url_encode(hex2bin('04'.$x.$y));
}
$publicKey = generateVapidKeys();
And finally here is my Notification send:
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
function generate_jwt($headers, $payload, $privateKey){
$headers_encoded = base64url_encode(json_encode($headers));
$payload_encoded = base64url_encode(json_encode($payload));
//$signature = hash_hmac('SHA256', "$headers_encoded.$payload_encoded", $secret, true);
openssl_sign("$headers_encoded.$payload_encoded", $signature, $privateKey, OPENSSL_ALGO_SHA256);
$signature_encoded = base64url_encode($signature);
return "$headers_encoded.$payload_encoded.$signature_encoded";
}
function is_jwt_valid($jwt, $publicKey){
$tokenParts = explode('.', $jwt);
// check the expiration time - note this will cause an error if there is no 'exp' claim in the jwt
$expires = json_decode(base64_decode($tokenParts[1]))->exp < time();//($expires - time()) < 0;
$signature = openssl_verify($tokenParts[0].'.'.$tokenParts[1], base64_decode($tokenParts[2]), $publicKey, OPENSSL_ALGO_SHA256);
if($expires || !$signature){
return false;
}
return true;
}
function generateVapidToken($url, $privateKey) {
$expiration = time() + (12 * 60 * 60); // 12 hours
$header = [
'alg' => 'ES256',
'typ' => 'JWT',
];
$body = [
'aud' => $url,
'exp' => $expiration,
'sub' => 'mailto:[email protected]',
];
return generate_jwt($header, $body, $privateKey);
}
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
// Assuming you have a database connection established
// Function to send a push notification
function sendPushNotification($subscription, $payload)
{
$parse = parse_url($subscription->endpoint);
$url = $parse['scheme'].'://'.$parse['host'];//.pathinfo(parse_url($parse['path'], PHP_URL_PATH))['dirname'];
echo $url.PHP_EOL.PHP_EOL;
$vapidKeys = json_decode(file_get_contents('vapid.json'));
//print_r(json_encode($vapidKeys, JSON_PRETTY_PRINT));
$keyPair = openssl_pkey_new([
'ec' => [
'digest_alg' => 'sha256',
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1', // P-256 curve
'x' => hex2bin($vapidKeys->x),
'y' => hex2bin($vapidKeys->y),
'd' => hex2bin($vapidKeys->d)
]
]);
$privateKeyDetails = openssl_pkey_get_details($keyPair);
openssl_pkey_export($keyPair, $privateKey);
$token = generateVapidToken($url, $privateKey);
//openssl_sign('HELLO WORLD', $signature, $privateKey, OPENSSL_ALGO_SHA256);
echo $token;
echo PHP_EOL;
echo PHP_EOL;
$publicKey = openssl_pkey_get_public($privateKeyDetails['key']);
$verified = is_jwt_valid($token, $publicKey);
//$verified = openssl_verify('HELLO WORLD', $signature, $publicKey, OPENSSL_ALGO_SHA256);
echo 'Token Valid: '.(($verified) ? "TRUE" : "FALSE");
echo PHP_EOL;
echo PHP_EOL;
$publicKey = base64url_encode(hex2bin('04'.$vapidKeys->x.$vapidKeys->y));
echo $publicKey;
echo PHP_EOL;
echo PHP_EOL;
$headers = [
//'Authorization: WebPush '.$token,
'Authorization: vapid t='.$token.',k='.$publicKey,
//'Authorization: key=' . $subscription->keys->auth,
//'Crypto-Key: p256ecdsa='.$publicKey.';dh='.$subscription->keys->auth,//$subscription->keys->p256dh,
'Content-Type: application/json',
];
/*
$notification = [
'title' => 'Your Notification Title',
'body' => 'Your Notification Body',
'icon' => 'path/to/icon.png',
];
*/
$data = [
'notification' => $payload,
//'applicationServerKey' => $vapidKeys->publicKey
];
$options = [
CURLOPT_URL => $subscription->endpoint,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
];
$ch = curl_init();
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
if ($result === false) {
echo 'Error: ' . curl_error($ch) . PHP_EOL;
} else {
echo 'Push notification sent successfully!' . PHP_EOL;
}
print_r($result);
}
// Example payload
$notificationPayload = [
'title' => 'New Notification',
'body' => 'This is the body of the notification.',
'icon' => 'icon.png'
];
if(file_exists('endpoints.json')){
$subscriptions = json_decode(file_get_contents('endpoints.json'));
// Send push notifications to all stored subscriptions
foreach ($subscriptions as $subscription) {
sendPushNotification($subscription, $notificationPayload);
}
}
?>
2
Answers
The answer for this specific issue was the way I was generating the JWT.I needed to modify the signature differently.
Your procedure is wrong.
To make push API working we should.
private key
to safe place on server.pubic key
is require for create subscription in browser you can save them in javascript code or any method.browser/user agent
(it’s done by end user) and send to server save them to DB or any storage.http request
containnotification payload
(payload might different for eachbrowser/user agent
) follow rfc standard and send them toendpoint
in subscription.Here some resources
Don’t confuse between Push API and Notification API.
It’s different API. We could Push but don’t need to use pushed data in Notification API. So you could push any data from server and convert them to Notification on browser.