web-push-libs / pywebpush

Python Webpush Data encryption library
Mozilla Public License 2.0
303 stars 52 forks source link

Webpush Exception: Push failed: 403 Forbidden #118

Closed alexanderbeloiu closed 4 years ago

alexanderbeloiu commented 4 years ago

When I send a notification from my server it says WebPushException: Push failed: 403 Forbidden Response body:aud claim in the token MUST include the origin of the push resource URL If I send it from pythonanywhere (a cloud hosting service) it works sometimes, and other times it has the error above.

jrconlin commented 4 years ago

Short answer: include a dict containing the schema and host for the endpoint as vapid_claims e.g.

webpush(
    subscription_info={
        "endpoint": "https://example.com/v1/push...",
        "keys": { ... }
    }, 
    data="mary had a little lamb", 
    vapid_claims={
       "sub": "alex@example.org",
       "aud": "https://example.com",
       "exp": time.time()+300
   },
   vapid_private_key="..."
)

webpush() tries to guess what vapid_claims.aud should be from the subscription_info, but it's best to be explicit. If you want to verify what the VAPID claims are, you can turn on the verbose flag.

Longer answer: Yeah, some push providers are like that. Technically, the VAPID header is optional and the only thing that's really Not Optional is the sub field, but specifications have lots of words that can be hard to follow. (Sorry, that's not to you. Just a bit of general frustration.)

There are three components of the VAPID header: 1) sub -> your email address as a URL (e.g. alexander@example.org or whatever) 2) aud -> the Schema and Host of the push server you're talking to (e.g. if the push endpoint is https://example.com:8012/v1/push the aud would be https://example.com:8012/ 3) exp -> the UTC timestamp for when the VAPID header should expire. This one's tricky. A VAPID header can live for up to 24 hours, meaning you could reuse the same header for multiple messages. If you're not sending out thousands of messages a second, feel free to use something simple like time.time() + 300

everything except aud is handled for you by the py_vapid library, which pywebpush calls. pywebpush tries to figure out the aud by looking in the subscription_info if aud isn't present. Often, it's just better to specify the values rather than use the "quick and easy" route.

If you don't mind, what is the WebPush Provider you're using? I'd like to know the various ones out there and the quirks they have.

alexanderbeloiu commented 4 years ago

Thanks! I use Google Cloud Messaging and Mozilla push as a WebPush Provider.

Alexerson commented 1 year ago

As this is the main result on Google for this error message, here is why this was happening "randomly" for me.

The dict you are passing to the lib is completed as is by this library, and therefore if you’re just passing a dict from your settings, like:

webpush(
  subscription_info=subscription,
  data=json.dumps(payload),
  vapid_private_key=settings.VAPID_PRIVATE_KEY,
  vapid_claims=settings.VAPID_CLAIMS,
)

, in subsequent calls, the settings.VAPID_CLAIMS will contain an aud key already, and pywebpush will not try to complete it. Depending on if this dict was overridden or not, the behaviour will differ and sometimes it will work.

For me, the fix was as simple as replacing the last line by: vapid_claims=settings.VAPID_CLAIMS.copy().

(This is mentionned in the doc, and discussed in #130 )

zehawki commented 1 year ago

Also this - https://github.com/web-push-libs/pywebpush/issues/130#issuecomment-1605726181