Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ $report = $webPush->sendOneNotification(

### Authentication (VAPID)

Browsers need to verify your identity. A standard called VAPID can authenticate you for all browsers. You'll need to create and provide a public and private key for your server. These keys must be safely stored and should not change.
Browsers need to verify your identity. A standard called VAPID can authenticate you for all browsers based on [RFC8292](https://www.rfc-editor.org/rfc/rfc8292).
You'll need to create and provide a public and private key for your server. These keys must be safely stored and should not change.

According to the standard it is optional to provide contact details by the `subject` property.
In practice all browsers require a valid `subject` which can contain an email address or an available https website. It should not change.
Please note that browser manufacturers may use additional verification methods to prevent abuse of the push service.

You can specify your authentication details when instantiating WebPush. The keys can be passed directly (recommended), or you can load a PEM file or its content:

Expand All @@ -144,19 +149,27 @@ use Minishlink\WebPush\WebPush;
$endpoint = 'https://fcm.googleapis.com/fcm/send/abcdef...'; // Chrome

$auth = [
'VAPID' => [
'subject' => 'mailto:[email protected]', // can be a mailto: or your website address
'publicKey' => '~88 chars', // (recommended) uncompressed public key P-256 encoded in Base64-URL
'privateKey' => '~44 chars', // (recommended) in fact the secret multiplier of the private key encoded in Base64-URL
'pemFile' => 'path/to/pem', // if you have a PEM file and can link to it on your filesystem
'pem' => 'pemFileContent', // if you have a PEM file and want to hardcode its content
'VAPID' => [ // Recommended.
'subject' => 'mailto:[email protected]', // Must be an email beginning with mailto: or available https website address.
'publicKey' => '~88 chars', // Uncompressed public key P-256 encoded in Base64-URL.
'privateKey' => '~44 chars', // In fact the secret multiplier of the private key encoded in Base64-URL.
],
'VAPID' => [ // Alternative 1.
'subject' => 'mailto:[email protected]', // Must be an email beginning with mailto: or available https website address.
'pemFile' => 'path/to/pem', // If you have a PEM file and can link to it on your filesystem.
],
'VAPID' => [ // Alternative 2.
'subject' => 'mailto:[email protected]', // Must be an email beginning with mailto: or available https website address.
'pem' => 'pemFileContent', // If you have a PEM file and want to hardcode its content.
],
];

$webPush = new WebPush($auth);
$webPush->queueNotification(...);
```

#### Create VAPID keys

In order to generate the uncompressed public and secret key, encoded in Base64, enter the following in your Linux bash:

```bash
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ parameters:
paths:
- src
reportUnmatchedIgnoredErrors: false
treatPhpDocTypesAsCertain: false
ignoreErrors:
- identifier: missingType.iterableValue
strictRules:
Expand Down
31 changes: 24 additions & 7 deletions src/VAPID.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,30 @@
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\Serializer\CompactSerializer;

/**
* @phpstan-type VapidConfig array{
* subject: string,
* publicKey: string, // ~88 chars Base64URL
* privateKey: string // ~44 chars Base64URL
* }
* @phpstan-type VapidPemFileConfig array{
* subject: string,
* pemFile: string // path/to/pem
* }
* @phpstan-type VapidPemConfig array{
* subject: string,
* pem: string // PEM file content
* }
* @phpstan-type InputVapidConfig VapidConfig|VapidPemFileConfig|VapidPemConfig
*/
class VAPID
{
private const PUBLIC_KEY_LENGTH = 65;
private const PRIVATE_KEY_LENGTH = 32;

/**
* @param InputVapidConfig $vapid
* @return VapidConfig
* @throws \ErrorException
*/
public static function validate(array $vapid): array
Expand All @@ -36,19 +54,19 @@ public static function validate(array $vapid): array
$vapid['pem'] = file_get_contents($vapid['pemFile']);

if (!$vapid['pem']) {
throw new \ErrorException('Error loading PEM file.');
throw new \ErrorException('[VAPID] Error loading PEM file.');
}
}

if (isset($vapid['pem'])) {
$jwk = JWKFactory::createFromKey($vapid['pem']);
if ($jwk->get('kty') !== 'EC' || !$jwk->has('d') || !$jwk->has('x') || !$jwk->has('y')) {
throw new \ErrorException('Invalid PEM data.');
throw new \ErrorException('[VAPID] Invalid PEM data.');
}

$binaryPublicKey = hex2bin(Utils::serializePublicKeyFromJWK($jwk));
if (!$binaryPublicKey) {
throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary');
throw new \ErrorException('[VAPID] Failed to convert VAPID public key from hexadecimal to binary.');
}
$vapid['publicKey'] = base64_encode($binaryPublicKey);
$vapid['privateKey'] = base64_encode(str_pad(Base64Url::decode($jwk->get('d')), self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT));
Expand Down Expand Up @@ -113,7 +131,6 @@ public static function getVapidHeaders(
'alg' => 'ES256',
];


try {
$jwtPayload = json_encode(
[
Expand Down Expand Up @@ -161,7 +178,7 @@ public static function getVapidHeaders(
}

// @phpstan-ignore deadCode.unreachable
throw new \ErrorException('This content encoding is not supported');
throw new \ErrorException('This content encoding is not supported.');
}

/**
Expand All @@ -176,12 +193,12 @@ public static function createVapidKeys(): array

$binaryPublicKey = hex2bin(Utils::serializePublicKeyFromJWK($jwk));
if (!$binaryPublicKey) {
throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary');
throw new \ErrorException('Failed to convert VAPID public key from hexadecimal to binary.');
}

$binaryPrivateKey = hex2bin(str_pad(bin2hex(Base64Url::decode($jwk->get('d'))), 2 * self::PRIVATE_KEY_LENGTH, '0', STR_PAD_LEFT));
if (!$binaryPrivateKey) {
throw new \ErrorException('Failed to convert VAPID private key from hexadecimal to binary');
throw new \ErrorException('Failed to convert VAPID private key from hexadecimal to binary.');
}

return [
Expand Down