Crafting Trustworthy Notifications: How to Implement Server-Side VAPID WebPush with AdsPush

Anıl Dursun ŞENEL
adessoTurkey
Published in
6 min readAug 29, 2023

In the dynamic landscape of app development, notifications play a pivotal role in connecting with users. However, with increased connectivity comes the need for heightened security and trust. This is where the innovation of VAPID (Voluntary Application Server Identification) with WebPush steps in. In this article, we’ll embark on a journey to understand and implement server-side VAPID WebPush using the AdsPush library. Through practical insights and examples, we’ll explore how this technique enhances the delivery of notifications while ensuring authenticity and security.

Unveiling the Essence of VAPID

Before we dive into implementation, let’s grasp the concept of VAPID. VAPID, or Voluntary Application Server Identification, introduces a standardized way to authenticate your server’s identity to push services. This is achieved by cryptographically signing your push messages, allowing the recipient’s device to verify the source of the notification. In essence, VAPID enhances both the security and trustworthiness of your notifications, fostering a more secure environment for user interaction.

Understanding AdsPush: Your Ally in Push Notifications

As we delve into the world of server-side VAPID WebPush, let’s meet your partner in this journey — AdsPush. An open-source NuGet package, AdsPush serves as your bridge to sending server-side notifications while ensuring ease of use and robust functionality. With full support for APNS, FCM, and VAPID WebPush, AdsPush offers a comprehensive solution for your push notification needs. Its abstraction simplifies complex processes, making it accessible to developers across varying skill levels.

In the upcoming sections, we’ll transition from theory to practice, exploring how to integrate VAPID WebPush using AdsPush. We’ll navigate through a sample JavaScript WebPush client project and demonstrate how AdsPush streamlines the process of incorporating this technique into your application. By the end of this journey, you’ll be well-equipped to implement server-side VAPID WebPush, enhancing both the engagement and security of your app.

In this article, I’ll be skipping the installation and configuration sections of AdsPush for brevity. If you’re looking for more detailed information about this aspect, you can refer to the following dedicated article.

AdsPush: Sending Server-Side Push Notifications Using .NET

Let’s Jomb the Code

Generating VAPID Keys Using Bash Commands

Before delving into the implementation, let’s create the essential VAPID keys: the public and private keys. These keys play a crucial role in authenticating your server and ensuring secure communication. Here’s how you can generate them using bash commands:

Public Key Generation:

openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem
openssl ec -in vapid_private.pem -pubout -out vapid_public.pem

Private Key Generation:

openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem

After running these commands, you’ll have the vapid_private.pem file containing your private key, and the vapid_public.pem file containing your public key.

Remember, safeguard these keys as they grant access to your server. Now that we have our keys ready, let’s seamlessly integrate VAPID WebPush with AdsPush.

In the next section, we’ll explore how to incorporate these keys into your AdsPush implementation and elevate the security of your notification delivery.

Additionally, if you prefer a code-side approach for generating keys, AdsPush offers a convenient solution. You can utilize the VapidHelper.GenerateVapidKeys() method provided by AdsPush to dynamically generate your VAPID keys directly within your codebase. This method streamlines the process, eliminating the need for external tools or scripts. By invoking this method, you can programmatically generate the required VAPID public and private keys, enhancing the efficiency of your implementation.

Configuring Sample Client App

As we venture into configuring our sample client app, it’s essential to understand the role of Service Workers in modern web development. A Service Worker is a background JavaScript script that empowers you to manage network requests, caching, and more, contributing to enhanced user experiences. By incorporating Service Workers into your app, you can enable powerful features like push notifications and offline access.

With this foundation in place, let’s proceed to set up the sample client app with a Service Worker. We’ll be utilizing the Service Worker to facilitate the VAPID-secured push notifications using the keys we generated earlier. This integration will pave the way for secure and seamless communication between your server and the user’s device.

In the upcoming steps, we’ll walk through the process of integrating the Service Worker, and later, we’ll delve into AdsPush integration for a comprehensive push notification solution. Let’s get started!

And now, let’s dive into the code! Below, you’ll find the sample Service Worker implementation that sets the stage for our VAPID-enabled push notifications. Take a moment to explore the intricacies of this essential script.

But that’s not all — next up, we have our sample HTML page. This page requests the user’s notification permission, a pivotal step in establishing a seamless communication channel. Let’s move on to this crucial part

self.addEventListener("push", (event) => {
if (!(self.Notification && self.Notification.permission === "granted")) {
return;
}

const data = event.data?.json() ?? {};
const icon = "icon.png";

const options = {
lang: data.lang || "en-US",
title: data.title,
body: data.message,
tag: data.tag,
silent: data.silent,
image: data.image,
vibrate: [200, 100, 200],
actions: data.actions || [],
icon,
data: {
url: data.click_action
}
};

//do your operations
if (options.silent)
return;
event.waitUntil(self.registration.showNotification(data.title, options));

});

self.addEventListener('notificationclick', function (event) {
event.notification.close(); // Bildirimi kapat
if (clients.openWindow && event.notification.data.url) {
event.waitUntil(clients.openWindow(event.notification.data.url));
}
});

self.addEventListener('install', function (event) {
self.skipWaiting();
});

self.addEventListener('activate', function (event) {
event.waitUntil(clients.claim()); // Hemen etkinleşmesini sağla
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="icon.png">
<link rel="apple-touch-icon" sizes="180x180" href="icon-180.png">
<link rel="manifest" href="manifest.json">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<title>AdsPush Vapid Client Test</title>

</head>
<body class="bg-dark" data-bs-theme="dark">
<div class="container my-5">
<h1 class="mb-4">AdsPush Sample App</h1>
<button id="subscribeButton" class="btn btn-primary mb-3">Abone Ol</button>
<div class="mb-3">
<label for="endpoint" class="form-label">Endpoint:</label>
<div class="input-group">
<textarea id="endpoint" rows="3" readonly class="form-control"></textarea>
<button class="btn btn-outline-primary" onclick="copyContent('endpoint')">Kopyala</button>
</div>
</div>
<div class="mb-3">
<label for="p256dh" class="form-label">p256dh:</label>
<div class="input-group">
<textarea id="p256dh" rows="3" readonly class="form-control"></textarea>
<button class="btn btn-outline-primary" onclick="copyContent('p256dh')">Kopyala</button>
</div>
</div>
<div class="mb-3">
<label for="auth" class="form-label">auth:</label>
<div class="input-group">
<textarea id="auth" rows="3" readonly class="form-control"></textarea>
<button class="btn btn-outline-primary" onclick="copyContent('auth')">Kopyala</button>
</div>
</div>
</div>
<script>
document.querySelector("#subscribeButton").addEventListener("click", async () => {
try {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
alert("Permission denied")
return;
}
const subscription = await subscribeUserToPush();
sendSubscriptionToServer(subscription);
} catch (error) {
alert("Permission was not granted or subscription failed")
console.error('Permission was not granted or subscription failed:', error);
}
});

async function subscribeUserToPush() {
const registration = await navigator.serviceWorker.register('service-worker.js');
const subscribeOptions = {
userVisibleOnly: true,
// Replace 'your_public_key_here' with your actual public key,
// and make sure not to include the 'begin' and 'end' tags.
applicationServerKey: urlBase64ToUint8Array('your_public_key_here')
};
return registration.pushManager.subscribe(subscribeOptions);
}

function sendSubscriptionToServer(subscription) {
console.log(JSON.stringify(subscription));
let subObj = JSON.parse(JSON.stringify(subscription))
document.querySelector("#endpoint").value = subObj.endpoint;
document.querySelector("#p256dh").value = subObj.keys.p256dh;
document.querySelector("#auth").value = subObj.keys.auth;
// Send subObj to your server here.
}

function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}

function copyContent(elementId) {
const element = document.getElementById(elementId);
element.select();
document.execCommand("copy");
}

</script>
</body>
</html>

You can access full code from here

Using AdsPush

// Configure VAPID settings for AdsPush
var vapidSettings = new AdsPushVapidSettings()
{
// Replace with your actual VAPID public key
PublicKey = "your_public_key_here",

// Replace with your actual VAPID private key
PrivateKey = "your_private_key_here"
};

var sender = builder
.ConfigureVapid(vapidSettings)
.BuildSender();

//Comming from client.
string
endpoint = "https://fcm.googleapis.com/fcm/send/cIo6QJ4MMtQ:APA91bEGHCpZdHaUS7otb5_xU1zNWe6TAqria9phFm7M_9ZIiEyr0vXj3gRHbeIJMYvp2-SAVbgNrVvl7uBvU_VTLpIA0CLBcmqXuuEktGr0U4LVLvwWBibO68spJk7D-lr8R9zPyAXE",
p256dh = "BIjydse4Rij892SJN10xx1qbxDM6GrYXSfg7TGu90CVM1WmlTYzn_79psRqseyWdER969LGLjZmnXIhHPaKTyGE",
auth = "TkLGLzFeUU3C9SJJN6dLAA";

var subscription = VapidSubscription.FromParameters(endpoint, p256dh, auth);
//Sending basic notification
var basicPayload = new AdsPushBasicSendPayload()
{
Title = AdsPushText.CreateUsingString("test"),
Detail = AdsPushText.CreateUsingString("detail"),
Badge = 52,
Sound = "default",
Parameters = new Dictionary<string, object>()
{
{
"pushParam1", "value1"
},
{
"pushParam2", "value2"
},
}
};

await sender.BasicSendAsync(
AdsPushTarget.BrowserAndPwa,
subscription.ToAdsPushToken(),
basicPayload);

//Accessing full patform option
var vapidResult = await sender
.GetVapidSender()
.SendAsync(
subscription,
new VapidRequest()
{
Title = "",
Badge = "",
Message = "",
Sound = "",
Icon = "",
Image = "",
Language = "",
Silent = false,
Tag = "",
ClickAction = "",
VibratePattern = "",
Data = new Dictionary<string, string>()
{
{
"param1", "value1"
}
}
});

Conclusion

In closing, this article has introduced the powerful realm of server-side VAPID WebPush with the invaluable aid of AdsPush. By seamlessly integrating VAPID authentication into your notifications, you’ve fortified the security and trustworthiness of your communication. AdsPush, an open-source gem, extends an accessible bridge between your applications and users across diverse platforms, empowering you to forge engaging and secure user experiences.

As an open-source tool, AdsPush fosters a collaborative environment. It not only simplifies complex processes but also invites contributions from the developer community. This synergy propels the continuous evolution of this library, ensuring that you’re equipped with cutting-edge solutions for your push notification needs. The journey of exploration and implementation continues, and with AdsPush, you’re well-positioned to make notifications a standout feature of your applications.

--

--