Skip to content

Instantly share code, notes, and snippets.

@leegeunhyeok
Last active Jul 31, 2020
Embed
What would you like to do?
๐Ÿ”” Web Push - VAPID verification example
// VAPID - Voluntary Application Server Identification for Web Push
// Step-by-Step Verification example
/*
[[ Web Push Archtecture with VAPID verification ]]
@author leegeunhyeok <dev.ghlee@gmail.com>
[0]Has VAPID Key Pair
| [7]
V [6] Verify request
+-------------+ (Request Push Message) using Public key
| Application | Sign JWT with +--------------+
| Server |---- VAPID Private key ---->| Push Service | [3]Register Device and
+-------------+ + +--------------+ store VAPID Public key
^ | User Subscription ^ | |
| | | | |
[5]Subscription | | [1]Distribute VAPID Public key | | |
| | | | |
| V | | |
+--------+ [2]Subscribe + VAPID Public key | | | [8]Send push message to UA
| User |------------------------------------+ | | if request is valid
| Agent |<-------------------------------------+ |
+--------+ [4] Subscription |
^ |
| |
+--------------------------------------------+
**/
const webpush = require('web-push')
const urlBase64 = require('urlsafe-base64')
const asn1 = require('asn1.js')
const jws = require('jws')
const ECPublicKeyASN = asn1.define('ECPublicKey', function () {
this.seq().obj(
this.key('algorithm').seq().obj(
this.key('id').objid(),
this.key('curve').objid()
),
this.key('pub').bitstr()
)
})
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function() {
this.seq().obj(
this.key('version').int(),
this.key('privateKey').octstr(),
this.key('parameters').explicit(0).objid().optional()
)
})
// VAPID Public key to pem
function getPublicPEM (key) {
return ECPublicKeyASN.encode({
algorithm: {
id: [1, 2, 840, 10045, 2, 1],
curve: [1, 2, 840, 10045, 3, 1, 7]
},
pub: {
unused: 0,
data: key
}
}, 'pem', {
label: 'PUBLIC KEY'
})
}
// VAPID Private key to pem
function getPrivatePEM (key) {
return ECPrivateKeyASN.encode({
version: 1,
privateKey: key,
parameters: [1, 2, 840, 10045, 3, 1, 7] // prime256v1
}, 'pem', {
label: 'EC PRIVATE KEY'
})
}
function line (end = '') {
console.log('==============================' + end)
}
// Generate VAPID pair
const vapidKeys = webpush.generateVAPIDKeys()
// JSON Header
const header = {
typ: 'JWT',
alg: 'ES256'
}
// JSON Payload
const payload = {
aud: 'https://github.com',
exp: new Date().getTime(),
sub: 'mailto:dev.ghlee@gmail.com'
}
// VAPID to pem
const vapidKeysPem = {
publicKey: getPublicPEM(urlBase64.decode(vapidKeys.publicKey)),
privateKey: getPrivatePEM(urlBase64.decode(vapidKeys.privateKey))
}
// Sign with VAPID Private key
const token = jws.sign({
header,
payload,
privateKey: vapidKeysPem.privateKey
})
console.log('VAPID Public Key:', vapidKeys.publicKey)
console.log('VAPID Private Key:', vapidKeys.privateKey)
line()
console.log('VAPID Public pem\n', vapidKeysPem.publicKey)
line()
console.log('VAPID Private pem\n', vapidKeysPem.privateKey)
line()
console.log('JSON Header', header)
console.log('JSON Payload', payload)
line()
console.log('Signed JWT:', token)
line()
// Verify with public key
const ok = jws.verify(token, header.alg, Buffer.from(vapidKeysPem.publicKey))
console.log('Verified:', ok)
line()
let result
try {
// Verify with wrong public key
result = jws.verify(token, header.alg, Buffer.from(vapidKeysPem.publicKey + '000'))
} catch {
result = false
}
console.log('Verified:', result)
/*
[[ Result ]]
VAPID Public Key: BB9djjdQ236nQNnKTFfKuRZ9lqYKS3gQomKAN1LG2I76kN8ghMNqKk1QiwC_phNnmGlV-GgtGSMbx8-fHrHnK4A
VAPID Private Key: SARF5d1o5aVgHIbs9foU56ZhqpIrRxo-wsYVPCDA5B0
==============================
VAPID Public pem:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEH12ON1DbfqdA2cpMV8q5Fn2WpgpLeBCiYoA3UsbYjvqQ3yCEw2oqTVCLAL+mE2eYaVX4aC0ZIxvHz58esecrgA==
-----END PUBLIC KEY-----
==============================
// VAPID Private pem:
-----BEGIN EC PRIVATE KEY-----
MDECAQEEIEgEReXdaOWlYByG7PX6FOemYaqSK0caPsLGFTwgwOQdoAoGCCqGSM49AwEH
-----END EC PRIVATE KEY-----
==============================
JSON Header { typ: 'JWT', alg: 'ES256' }
JSON Payload { aud: 'https://github.com',
exp: 1591003437177,
sub: 'mailto:dev.ghlee@gmail.com' }
==============================
Signed JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dpdGh1bi5jb20iLCJleHAiOjE1OTEwMDM0MzcxNzcsInN1YiI6Im1haWx0bzpkZXYuZ2hsZWVAZ21haWwuY29tIn0.-tmFgevHgt88tjqIrS1Oa4K-Bd3KblXhFJ09YYISB9G9--qiHZ-kFyMrLcEzF3JhxIfC6QIYCoiask9oovvqoQ
==============================
Verified: true
==============================
Verified: false
**/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment