Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web Push with VAPID: 400/401 Unauthorized Registration

First of all, I've already check these questions, without any luck:

  • Web Push API Chrome, returning "Unauthorized Registration"
  • [WebPush][VAPID] Request fails with 400 UnauthorizedRegistration

I am trying to implement web push notifications for a web app that I'm working on. Currently, I've achieved the following goals:

  • Create a VAPID keys pair (with this guide).
  • Register a service worker (only a service worker, I think that manifest.json is no longer required).
  • Subscribe the user to the server (subscription data will be stored in the database).
  • Send a push notification with webpush gem.

After setting it up, everything works fine (on localhost and on a remote machine). However, after some hours (between 12 and 24 hours), the notifications stop working on the remote machine (localhost works perfectly). After that time, the following errors are thrown when sending the push notifications from the server side (Rails):

  • Chrome: UnauthorizedRegistration 400 (no extra info).
  • Firefox: {"code": 401, "errno": 109, "error": "Unauthorized", "more_info": "http://autopush.readthedocs.io/en/latest/http.html#error-codes", "message": "Request did not validate Invalid bearer token: Auth > 24 hours in the future"}

After this error appeared, I tried to unsubscribe and resubscribe the user on every page visit. The subscription field on the db is updated after the resubscription is done, but the error is still being thrown, with the same information. No errors are thrown on the browser, nor service worker.

I've tried to force the resubscription manually by placing some js code on the Chrome Dev Tools console, unregistering the service worker, and reseting the push notification permissions, but nothing solves the error. I can only fix this error by creating a new VAPID key pair rebooting the remote machine. After crebooting the machine, I've another 12-24 hours before it fails again. Also, the notifications send process don't work neither on rails server (nginx + unicorn), rails console, nor irb.

I don't know where to go from now. Even worst, I can only try fixes once a day, since it'll break every 24 hours. I think I need some external help with a fresh vision of the problem. Am I missing something? Is there any OS dependency that VAPID uses and needs to be restarted?


Here are some code snippets that may be useful. Sorry for the mess, but I've done tons of modifications to try making it work.

Service worker registration:

serviceWorkerRegistration = null

registerServiceWorker = ->
  if 'serviceWorker' of navigator && 'PushManager' of window
    navigator.serviceWorker.register("<js url>").then (reg) ->
      serviceWorkerRegistration = reg
      checkSubscription()

    .catch (error) ->
      console.warn 'Service Worker Error', error

  else
    console.warn 'Push messaging is not supported'

registerServiceWorker()

User subscription and resubscription

# Refresh user subscription (unsub + sub)
refreshUserSubscription = ->
  unsubscribeUser(subscribeUser)

# Subscribe the user to the push server
subscribeUser = ->
  return if !serviceWorkerRegistration

  # Subscribe
  serviceWorkerRegistration.pushManager.subscribe
    userVisibleOnly: true
    applicationServerKey: urlB64ToUint8Array('...')

  .then (subscription) ->
    pushSubscription subscription

  .catch (err) ->
    console.warn 'Failed to subscribe the user: ', err

# Unsubscribe user
unsubscribeUser = (callback)->
  return unless serviceWorkerRegistration

  serviceWorkerRegistration.pushManager.getSubscription().then (subscription) ->
    if subscription
      subscription.unsubscribe().then(callback)
    else
      callback()
  .catch (error) ->
    console.warn 'Error unsubscribing', error

# Push subscription to the web app
pushSubscription = (subscription) ->
  data = if subscription then subscription.toJSON() else {}
  $.post "<back-end endpoint>", subscription: data

# Fetch current subscription, and push it.
checkSubscription = () ->
  serviceWorkerRegistration.pushManager.getSubscription()
    .then (subscription) ->
      pushSubscription(subscription)

# I think that this should be done with promises instead of a simple timeout.
setTimeout refreshUserSubscription, 1000

Service worker:

self.addEventListener 'push', (event) ->
  title = "Example"
  options = { body: "Example" }

  notificationPromise = self.registration.showNotification(title, options)
  event.waitUntil(notificationPromise)

Web push call:

webpush = WebPush.new({ endpoint: '...', keys: { p256dh: '...', auth: '...' } })
webpush.set_vapid_details(
  "mailto:#{CONTACT_EMAIL}",
  "<base64 public key>",
  "<base64 private key>"
)
webpush.send_notification("foo")
like image 478
Wikiti Avatar asked Apr 26 '17 11:04

Wikiti


2 Answers

After changing the expiration time from 24 hours (default) to 12 hours, it now works properly:

Webpush.payload_send(
  message: "Hi!",
  endpoint: "...",
  p256dh: "...",
  auth: "...",
  vapid: {
    subject: "...",
    public_key: ENV['VAPID_PUBLIC_KEY'],
    private_key: ENV['VAPID_PRIVATE_KEY'],
    exp: 12.hours
  }
)
like image 112
Wikiti Avatar answered Oct 15 '22 03:10

Wikiti


It looks like the library has changed, this is how I fixed it:

def send_push(payload, endpoint, p256dh, auth)
  Webpush.payload_send(
    message: payload,
    endpoint: endpoint,
    p256dh: p256dh,
    auth: auth,
    vapid: {
      public_key: '...',
      private_key: '...',
      expiration: 12 * 60 * 60
    }
  )
end

Notice the key is expiration not exp and it's the expire time in seconds.

like image 1
David Parish Avatar answered Oct 15 '22 03:10

David Parish