Last active
May 28, 2021 00:18
-
-
Save stampycode/3c4734b7fab382a40f7e05edc2edd001 to your computer and use it in GitHub Desktop.
Create a ServiceWorker Push notification in PHP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* If you get any part of this process wrong, Google gives the really helpful error message "invalid JWT provided". | |
* | |
* Mozilla (Firefox) gives a slightly just-as-useful error: | |
* { | |
* "code": 401, "errno": 109, "error": "Unauthorized", | |
* "more_info": "http://autopush.readthedocs.io/en/latest/http.html#error-codes", | |
* "message": "Request did not validate Invalid Authorization Header" | |
* } | |
*/ | |
// Generate the keys like this, although you can probably do it in PHP. | |
// `openssl ecparam -genkey -name prime256v1 -noout -out server-push-ecdh-p256.pem &>/dev/null`; | |
// `openssl ec -in server-push-ecdh-p256.pem -pubout -out server-push-ecdh-p256.pub &>/dev/null`; | |
$privk = file_get_contents('server-push-ecdh-p256.pem'); | |
$pubk = file_get_contents('server-push-ecdh-p256.pub'); | |
$endpoint = "https://fcm.googleapis.com/fcm/send/some-really-long-unique-secret-string-that-your-web-client-gets-on-push-subscribe"; | |
$contact = "mailto:[email protected]"; | |
function base64web_encode($a) { | |
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($a)); | |
} | |
function base64web_decode($a) { | |
return base64_decode(str_replace(['-', '_', ''], ['+', '/', '='], $a)); | |
} | |
$asn = new phpseclib\File\ASN1(); | |
$header = [ | |
"typ" => "JWT", | |
"alg" => "ES256" | |
]; | |
$claims = [ | |
// just the https://hostname part | |
"aud" => substr($endpoint, 0, strpos($endpoint, '/', 10)), | |
// this push message will be discarded after 24 hours of non-delivery | |
"exp" => time() + 86400, | |
// who the server can talk to if our push script is causing problems | |
"sub" => $contact | |
]; | |
/* | |
* Note these need to be base64 url-safe encoded, not standard base64. | |
* @see https://tools.ietf.org/html/rfc4648#section-5 | |
*/ | |
$strHeader = base64web_encode(json_encode($header)); | |
$strPayload = base64web_encode(json_encode($claims)); | |
$toSign = $strHeader . '.' . $strPayload; | |
$signature = ''; | |
if (!openssl_sign($toSign, $signature, $privk, OPENSSL_ALGO_SHA256)) { | |
trigger_error('sign failed: '. openssl_error_string()); | |
} | |
/* | |
* openssl_sign produces a signature which is the hash wrapped in an | |
* ASN.1 structure, so we need to extract the 256-bit raw hash manually. | |
* There's no PHP function to do this, so we use a library. | |
*/ | |
$xx = $asn->decodeBER($signature); | |
/** @var \phpseclib\Math\BigInteger $a */ | |
/** @var \phpseclib\Math\BigInteger $b */ | |
$a = $xx[0]['content'][0]['content']; // 128-bits | |
$b = $xx[0]['content'][1]['content']; // 128-bits | |
$signature = $a->toBytes() . $b->toBytes(); | |
$strSignature = base64web_encode($signature); | |
/* | |
* This is now a complete JWT object. | |
*/ | |
$jwt = $strHeader . '.' . $strPayload . '.' . $strSignature; | |
/* | |
* Our PEM formatted public key is wrapped in an ASN.1 structure, so just | |
* like our signature above, lets extract | |
* the raw public key part, which is the bit we need. | |
*/ | |
$xx = $pubk; | |
$xx = str_replace(['-----BEGIN PUBLIC KEY-----','-----END PUBLIC KEY-----',"\n"], '', $xx); | |
$xx = base64_decode($xx); | |
$xx = $asn->decodeBER($xx); | |
$xx = $xx[0]['content'][1]['content']; | |
$xx = substr($xx, 1); // need to strip the first char, which is not part of the key | |
$xx = base64web_encode($xx); | |
$pubkey = $xx; | |
/* | |
* We need to append the public key used for signing this JWT object, so | |
* the server can validate the JWT and compare the public key against the | |
* push-registration by the client, where we said which public key we would | |
* accept pushes from. | |
*/ | |
$headers = [ | |
"Authorization: vapid t=$jwt,k=$pubkey", | |
"Content-length: 0", | |
"Ttl: 86400", | |
]; | |
/** | |
* Push! | |
*/ | |
$ch = curl_init($endpoint); | |
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_FAILONERROR, true); | |
curl_setopt($ch, CURLOPT_POST, 1); | |
curl_exec($ch); | |
$ct = curl_multi_getcontent($ch); | |
echo curl_error($ch); | |
curl_close($ch); | |
echo $ct; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment