I am currently coding a service on a perl server, that shall send a request to the Firebase Cloud Messaging API, which will then send push notifications to an app instance.
Since FCM is part of the Google API family, an OAuth2 token is required to access the API. During my research I found this perl solution. Because my service is running on an non-Google server environment, I can't use Google Application Default Credentials, but have to provide them manually, so I downloaded a json containing a private key following this description.
Reading the documentation of LWP::Authen::OAuth2 I got a little confused, where to put which parameter from the json into the $oauth2
object, because often different names are used to reference to the same values, like I suspect.
The json related to my firebase project:
{
"type": "service_account",
"project_id": "my_project_id",
"private_key_id": "some_key_id",
"private_key": "-----BEGIN PRIVATE KEY-----very_long_key-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-o8sf4@<my_project_id>.iam.gserviceaccount.com",
"client_id": "some_client_id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-o8sf4%40<my_project_id>.iam.gserviceaccount.com"
}
The implementation of the $oauth
object looks like this:
my $oauth2 = LWP::Authen::OAuth2->new(
client_id => "Public from service provider",
#probably that will be "some_client_id" from above
client_secret => "s3cr3t fr0m svc prov",
#the "very_long_key"?
service_provider => "Google",
#the "auth_uri"? That's what I would suggest here
#I've read some about the LWP::Authen::OAuth2::ServiceProvider module
#do I have to create an instance of that here?
#if so, which params do I need for that from the json?
redirect_uri => "https://your.url.com/",
#the FCM api I want to call?
# Optional hook, but recommended.
save_tokens => \&save_tokens,
save_tokens_args => [ $dbh ],
# This is for when you have tokens from last time.
token_string => $token_string.
#yes, i copy-pasted that from the docs
);
Now, as a beginner in Perl and a disliker of ambiguous key-values names, i'm a little confused, which value to put where and would be glad if anyone could help me with a guide here, what to put where even if this seems like very rookie question, it is important for me :D. So i'm thankful for every helpful answer!
EDIT
When trying to generate a JSON Web Token manually in my perl service using Crypt::JWT, i came across another trip wire, which made me doubt that the according authentication API from Google "https://www.googleapis.com/auth/firebase.messaging"
still accepts Bearer tokens ... I tried the generate my JWT, which seemed to be successful, but the request I sent to the actual FCM API then gave me this:
Request had invalid authentication credentials.
Expected OAuth 2 access token, login cookie
or other valid authentication credential
In the response printed as String I then found this little guy, which confused me a lot:
Client-Warning: Unsupported authentication scheme 'bearer'
Now I'm very unsure, it bearer tokens are still supported for the FCM API, even they are used in an example on the referring docs page. Does anyone have any up-to-date information about that? Thank you very much!
Digging deeper in various documentations and testing some things I realized, that LWP::Authen::OAuth2 is not only a bit of overhead, for creating a very tiny HTTPS request to an OAuth2 protected API, it is currently not possible either.
The caveat is hidden in the service_provider
, who hosts the authentication API, that I have to call to authenticate and authorize my service for accessing the actual Firecase Cloud Messaging API. In my case, this is Google, more precisely https://www.googleapis.com/auth/firebase.messaging
, also referred to as scope
in code.
Now one service provider can provide different authentication APIs for different clients. That's why some service providers require an additional parameter - the client_type
or type
respectively (for unambiguity I will use client_type
) - that also affects the authentication and authorization process via OAuth2.
When creating a new $oauth2
-object, and assigning a value to the field service_provider
an object of module LWP::Authen::OAuth2::ServiceProvider gets created, that may need also its parameters, like the scope
, to define, for which API family you want to be authorized and depending on that a client_type
.
Now Google is not a no-name Service Provider, so there already is a prebuild ServiceProvider module, explicitily for Google APIs : LWP::Authen::OAuth2::ServiceProvider::Google. This automatically fills in some parameters of a ServiceProvider object, and holds a hash of available client_types
to make sure you use one of them, because depending on the client_type
a specific sub-module of ServiceProvider::Google gets created internally.
So I tried to test that like so:
my $oauth2 = LWP::Authen::OAuth2->new(
service_provider => "Google",
scope => "https://www.googleapis.com/auth/firebase.messaging",
client_type => "service_account", #referring to 'type' in the json
client_id => "Public from service provider",
client_secret => "s3cr3t fr0m svc prov",
redirect_uri => "this-server.mydomain.com"
);
Follwing the further description here and sending a request with the built-in LWP::UserAgent object I still got an 401 error UNAUTHENTICATED
which confused me a lot. So when I read the documentation the I-don't-know-what time I stepped across this tiny line in the client_types
chapter of ServiceProvider::Google:
Service Account
This client_type is for applications that login to the developer's account using the developer's credentials. See https://developers.google.com/accounts/docs/OAuth2ServiceAccount for Google's documentation.
This is not yet supported, and would require the use of JSON Web Tokens to support.
Yep, that kinda knocks out the usage of the whole LWP:Authen::OAuth family to access Firebase APIs, as almost all of them have the client_type => "service_account"
. Now I have a deadline for my project and can't wait for the module to get extended or extending it myself would exceed my perl skills.
But between tears of desperation there is always a glimpse of hope, as I found this Google docs page with a live-saving addendum, that it's possible to use a JSON Web Token as a Bearer token. Researching for that I found more than one Perl solution to generate JWT from JSONs like a service account, and also this very helpful Stackoverflow answer that showed me the way out of the choke point.