Hello Abhishek,
For the initial look, the above suggestion comments are helpful, and building on that, Begin by re-chcking that the "project_id"
in the downloaded JSON matches exactly what you see under Project Settings > General > Project ID in the Firebase console and check the FCM Sender ID there is identical to the Sender ID configured in Azure under Notification Hub > Push > Firebase (FCM). Even a single extra space or different letter case will cause Google’s OAuth server to reject your JWT assertion, producing a 401 error.
Next, verify that the .NET server’s system clock is accurately synchronized. Google’s OAuth2 endpoint enforces strict timestamp validation, so if your server’s clock drifts by more than a minute, the JWT will be considered expired or not yet valid. On Linux or macOS.
date -u
to see the current UTC time. If it is off, synchronize it with Google’s NTP server:
sudo ntpdate -u time.google.com
On Windows, confirm that the system time is set to sync with an Internet time server. This step prevents invalid_token errors caused by clock skew.
To pinpoint why Google is refusing the credential, enable detailed HTTP logging in your .NET application so you can capture the entire POST request to https://oauth2.googleapis.com/token
. You should see the form data including grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
and an assertion=<BASE64URL_JWT>
. If Google’s response includes an error such as "invalid_grant"
with a description like “Invalid issuer” or “Invalid scope,” you will know whether the scope is incorrect, the aud
(audience) is wrong, or the timestamp is off. For a quick manual test, you can build a minimal JWT with your service account key and run:
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=YOUR_BASE64URL_JWT" \
https://oauth2.googleapis.com/token
If Google returns something like {"error":"invalid_grant","error_description":"Token used too early"}
, you know it is a timestamp issue; if it says "unauthorized_client"
or "invalid_scope"
, verify that the scope is exactly https://www.googleapis.com/auth/firebase.messaging
.
You should also confirm that the service account JSON belongs to the Firebase project where your Android app is registered. It is easy to have two Firebase projects with similar names, and using a key from one project against a different FCM setup will always be rejected. In the Firebase console under Project Settings > Service accounts, regenerate the key for the exact project listed in Project Settings > General and re-upload that JSON to Azure under Notification Hub > Push > Firebase (FCM).
While troubleshooting, isolate the Notification Hub integration by registering an FCM token directly against the Hub’s REST API (bypassing the ACS Chat UI SDK). First, generate a SAS token using Azure CLI:
az notification-hub authorization-rule keys list \
--resource-group YOUR_RESOURCE_GROUP \
--namespace-name YOUR_NAMESPACE \
--notification-hub-name YOUR_HUB \
--name DefaultFullSharedAccessSignature
Copy the returned primaryKey
and build a SAS token in your shell, replacing <HUB-ENDPOINT>
, <SAS-KEY-NAME>
, and <SAS-KEY>
accordingly:
untilExpiry=$(date -u -d "1 hour" '+%s')
audience="https://<HUB-ENDPOINT>/installations/installation123?api-version=2021-01"
stringToSign=$(printf "%s\n%s" "$audience" "$untilExpiry")
signature=$(printf "%s" "$stringToSign" | openssl sha256 -hmac "<SAS-KEY>" -binary | base64 | sed -e 's/+/%2B/g' -e 's/\//%2F/g' -e 's/=/%3D/g')
sasToken="SharedAccessSignature sr=${audience}&sig=${signature}&se=${untilExpiry}&skn=<SAS-KEY-NAME>"
Then issue the installation request:
curl -X PUT \
-H "Authorization: $sasToken" \
-H "Content-Type: application/json" \
https://<HUB-ENDPOINT>/installations/installation123?api-version=2021-01 \
-d '{
"installationId": "installation123",
"platform": "fcm",
"pushChannel": "YOUR_DEVICE_FCM_TOKEN",
"tags": ["exampleTag"]
}'
If that call also returns 401 Unauthorized, it confirms the Hub is rejecting your FCM credential take another look at JSON/project alignment or the JWT issuance. If it succeeds, then the ACS Chat UI SDK’s request is likely malformed, perhaps using the wrong scope or audience.
Verify in Azure that the Push Connections section explicitly lists Firebase Cloud Messaging (FCM) rather than legacy Google (GCM/FCM), because older hubs defaulted to GCM and will reject FCM v1 credentials. If your Notification Hub resides in a Virtual Network or behind NSGs, confirm that outbound traffic to oauth2.googleapis.com
and fcm.googleapis.com
is not blocked. Even if you can send test notifications directly from Firebase, Azure’s backend still needs to reach Google’s OAuth endpoints to exchange your JSON for a valid access token. Enable NSG flow logs or Azure Monitor diagnostics on your hub’s subnet to confirm there are no firewall rules preventing this.
Finally, if all else fails after verifying project_id/Sender ID alignment, clock synchronization, JWT correctness, and network connectivity create a brand-new Notification Hub in a different region (for example, if yours is in East US, try West US) and configure it for FCM v1 from scratch using the same service account JSON. Occasionally, a hub’s metadata can become corrupted, and spinning up a fresh hub immediately eliminates that variable.
Once you identify whether the failure originates in the JWT exchange (check Google’s OAuth error payload), the hub configuration, or a networking restriction, you can resolve the 401 error. Hope this helps!
If the answer is helpful, please click Accept Answer and kindly upvote it. If you have any further questions, please reply back.