Use firebase functions with Apple Pay - firebase

I want to integrate with Apple Pay on my website using the Apple Pay JS API, but my call to Apple is failing.
We are using Firebase Functions to run our server, and Firebase Hosting to host our website.
To do this, I need to request an Apple Pay Payments Session as described in https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session.
The way this seems to work is that I start a payments session client side on my website and configure this to call an endpoint on my server. This then calls Apple's servers with a merchant identifier certificate I've created, and I get the payments session back and return it to my website. I'm following these documentation pages:
https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/providing_merchant_validation
https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session
I am calling Apple like this:
const { validationUrl } = req.body
const axiosInstance = axios.create({
httpsAgent: new https.Agent({
cert: fs.readFileSync(`${__dirname}/merchantIdentity.crt.pem`),
key: fs.readFileSync(`${__dirname}/merchantIdentity.key.pem`),
rejectUnauthorized: false
})
})
const appleUrl = `https://${validationUrl}/paymentSession`
const validateMerchantData = {
merchantIdentifier: <my merchant id>,
displayName: "Mercado",
initiative: "web",
initiativeContext: process.env.CLIENT_URL.replace("https://", "")
}
const appleRes = await axiosInstance.post(appleUrl, validateMerchantData)
CLIENT_URL is the base URL of my client website
I am getting the following error at this point:
Error: getaddrinfo EAI_AGAIN https
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26)
at GetAddrInfoReqWrap.callbackTrampoline (node:internal/async_hooks:130:17)
In https://developer.apple.com/documentation/apple_pay_on_the_web/configuring_your_environment, the documentation says "You must register and verify all top-level domains and subdomains where you will display the Apple Pay button." It's not clear to me whether this means the website domain (the hosting one) or our functions domain. I have successfully registered and verified our hosting domain, but I couldn't manage to do this with the functions domain, which is the one I'm actually using to call Apple (obviously wouldn't do this from the client, and Apple specifically says not to do this.
This is because Apple calls https://<domain_name>/.well-known/some_file.txt to do the verification, but firebase functions are always of the form https://-<project_id>.cloudfunctions.net/<function_name>, so it's impossible to have an endpoint for /.well-known. My solution has been to use rewrites on my Firebase Hosting URL to call my applePay firebase function.
What could be causing the error, and how could I resolve?
PS: not interested in any answers suggesting to use something like Stripe for Apple Pay.

Related

Firebase 3rd-party AuthProvider (Google/Facebook/etc) login with chrome extension manifest v3

Manifest version 3 for Chrome extensions have been killing me lately. Been able to navigate around it so far, but this one has really stumped me. I'm trying to use Firebase authentication for a Chrome extension, specifically with 3rd party auth providers such as Google and Facebook. I've setup the Firebase configuration for Login with Google and created a login section in the options page of the Chrome extension and setup the Firebase SDK.
Now, there are two login options when using an auth provider, signInWithRedirect and signInWithPopup. I've tried both of these and both have failed for different reasons. signInWithRedirect seems like a complete dead end as it redirects to the auth provider, and when it attempts to redirect back to the chrome-extension://.../options.html page, it just redirects to "about:blank#blocked" instead.
When attempting to use signInWithPopup, I instead get
Refused to load the script 'https://apis.google.com/js/api.js?onload=__iframefcb776751' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
In v2, you could simply add https://apis.google.com to the content_security_policy in the manifest. But in v3, the docs say
"In addition, MV3 disallows certain CSP modifications for extension_pages that were permitted in MV2. The script-src, object-src, and worker-src directives may only have the following values:"
self
none
Any localhost source, (http://localhost, http://127.0.0.1, or any port on those domains)
So is there seriously no way for a Google Chrome extension to authenticate with a Google auth provider through Google's Firebase? The only workaround I can think of is to create some hosted site that does the authentication, have the Chrome extension inject a content script, and have the hosted site pass the auth details back to the Chrome extension through an event or something. Seems like a huge hack though and possibly subject to security flaws. Anyone else have ideas??
Although it was mentioned in the comments that this works with the Google auth provider using chrome.identity sadly there was no code example so I had to figure out myself how to do it.
Here is how I did it following this tutorial:
(It also mentions a solution for non-Google auth providers that I didn't try)
Identity Permission
First you need permission to use the chrome identity API. You get it by adding this to your manifest.json:
{
...
"permissions": [
"identity"
],
...
}
Consistent Application ID
You need your application ID consistent during development to use the OAuth process. To accomplish that, you need to copy the key in an installed version of your manifest.json.
To get a suitable key value, first install your extension from a .crx file (you may need to upload your extension or package it manually). Then, in your user data directory (on macOS it is ~/Library/Application\ Support/Google/Chrome), look in the file Default/Extensions/EXTENSION_ID/EXTENSION_VERSION/manifest.json. You will see the key value filled in there.
{
...
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgFbIrnF3oWbqomZh8CHzkTE9MxD/4tVmCTJ3JYSzYhtVnX7tVAbXZRRPuYLavIFaS15tojlRNRhfOdvyTXew+RaSJjOIzdo30byBU3C4mJAtRtSjb+U9fAsJxStVpXvdQrYNNFCCx/85T6oJX3qDsYexFCs/9doGqzhCc5RvN+W4jbQlfz7n+TiT8TtPBKrQWGLYjbEdNpPnvnorJBMys/yob82cglpqbWI36sTSGwQxjgQbp3b4mnQ2R0gzOcY41cMOw8JqSl6aXdYfHBTLxCy+gz9RCQYNUhDewxE1DeoEgAh21956oKJ8Sn7FacyMyNcnWvNhlMzPtr/0RUK7nQIDAQAB",
...
}
Copy this line to your source manifest.json.
Register your Extension with Google Cloud APIs
You need to register your app in the Google APIs Console to get the client ID:
Search for the API you what to use and make sure it is activated in your project. In my case Cloud Firestore API.
Go to the API Access navigation menu item and click on the Create an OAuth 2.0 client ID... blue button.
Select Chrome Application and enter your application ID (same ID displayed in the extensions management page).
Put this client ID in your manifest.json. You only need the userinfo.email scope.
{
...
"oauth2": {
"client_id": "171239695530-3mbapmkhai2m0qjb2jgjp097c7jmmhc3.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
}
...
}
Get and Use the Google Auth Token
chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
// console.log("token: " + token);
let credential = firebase.auth.GoogleAuthProvider.credential(null, token);
firebase.auth().signInWithCredential(credential)
.then((result) => {
// console.log("Login successful!");
DoWhatYouWantWithTheUserObject(result.user);
})
.catch((error) => {
console.error(error);
});
});
Have fun with your Firebase Service...

Firebase Error: Auth error from APNS or Web Push Service

After running the following line in node-js:
import * as admin from "firebase-admin";
import * as serviceAccount from "../../firebase_service_account_key.json";
const app = admin.initializeApp({
credential: admin.credential.cert(serviceAccount as any),
databaseURL: "https://my-app-path.firebaseio.com"
});
admin.messaging().send({
token: "known-good-token",
notification: {
title: "Test Push Note",
body: "Here is some text"
}
});
I'm getting the error:
Error: Auth error from APNS or Web Push Service
Raw server response:
"{
"error":{
"code":401,
"message":"Auth error from APNS or Web Push Service",
"status":"UNAUTHENTICATED",
"details"[
{
"#type":"type.googleapis.com/google.firebase.fcm.v1.FcmError",
"errorCode":"THIRD_PARTY_AUTH_ERROR"
},
{
"#type":"type.googleapis.com/google.firebase.fcm.v1.ApnsError",
"statusCode":403,
"reason":"InvalidProviderToken"
}
]
}
}"
I've added an "APNs Authentication Key" to my ios project under the Settings > Cloud Messaging section of Firebase. I've also properly downloaded and imported my service account json file.
In terms of research, I've tried looking up the errors.
For the InvalidProviderToken error, this answer seems to indicate I'm using an old token. This is totally possible, but the logs on my app and database appear to match, so it seems off.
As for the THIRD_PARTY_AUTH_ERROR, google gave me no hits. The closest thing I found was this, and the following text might be the culprit (EDIT: it's not the issue):
auth/unauthorized-domain
Thrown if the app domain is not authorized for OAuth operations for your Firebase project. Edit the list of authorized domains from the Firebase console.
Does anyone have anymore details on this error which might help me get to the bottom of it?
This error arises if your app setup for iOS has an error in any one of the following:
Found in Settings > General > Your Apps > iOS Apps:
App Store ID
Bundle ID
Team ID
When adding an APNs key (Uploading to Cloud Messaging > APNs Authentication Key):
Team ID (should auto set based off ios app info above)
Key Id (often is in the name of the key, best to grab when creating it)
Everything worked for me the other day, so all setup was fine. But today I got this error.
Here's what solved it for me:
Revoked APNs Key
Created new one and downloaded it
Deleted old one and Uploaded it to Firebase Dashboard / Settings /
Cloud Messaging
Gone to Settings / Service Accounts and generated new private key
Added it to my Cloud Functions project (renamed it to
service-account.json in my case)
Saved the files and deployed the functions: firebase deploy --only
functions
Did you call admin.initializeApp()?
There are many official samples.
See:
https://github.com/firebase/functions-samples/blob/master/fcm-notifications/functions/index.js#L20
https://github.com/firebase/functions-samples/tree/master/fcm-notifications
https://github.com/firebase/functions-samples
https://github.com/firebase/quickstart-js/tree/master/messaging
I had the same issue. The culprit was lowercase APNs teamId. Changing it to capital solved it.
Double check if you have uploaded your apns key on firebase console. That was my case.

Why do I get auth'ed when I test hitting my Firebase Cloud Function in a browser?

I have a Firebase Cloud Function that handles HTTP requests, using:
export const foo = functions.https.onRequest((req, res) => {
// etc.
}
When I hit the URL for it in a browser, I see a Google sign in page, listing my Google accounts. If I sign in, I then get a 403:
Error: Forbidden
Your client does not have permission to get URL /foo from this server.
Why? There's nothing about this in the docs that I can find. I'm on the free plan ("Spark"), if that makes any difference.
[edit]
I'm accessing the function using the URL:
https://us-central1-[project name].cloudfunctions.net/[function name]
There's no vanity URL.
The 403 message is originating from the main url https://us-central1-[project-name].cloudfunctions.net/ which is fully managed by Google.
It seems cloud functions does not have an error handling for non-existing functions name. Thus everything that is not created are treated the same way as a forbidden link. I don't know if this is an intended behavior but since the functions are running on a managed environment, there's not much handling of not existing functions against your project cloud function url.
The following statement from the link above explains it all:
"Cloud Functions run in a fully-managed, serverless environment where Google handles infrastructure, operating systems, and runtime environments completely on your behalf. Each Cloud Function runs in its own isolated secure execution context, scales automatically, and has a lifecycle independent from other functions. "
Hope this helps.

Fetching google sheets data from firebase "Billing account not configured" even if it's google api

I'm trying to fetch some data from a google spreadsheet. According to the firebase pricing it says that cloud functions are limited to Google services which means that this should work. However when I try to fetch the data I get the the error:
Error: getaddrinfo ENOTFOUND spreadsheets.google.com spreadsheets.google.com:443
at errnoException (dns.js:28:10)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:76:26)
I am trying with the following code:
export const fetchExcel = functions.https.onRequest((req, res) => {
const query = "https://spreadsheets.google.com/feeds/list/hidden/od6/public/values?alt=json"
https.get(query, resp => {console.log (resp)});
});
so I am not making an api call to a non-google service yet somehow I believe it's blocking me. How else can you do it?
Without a payment plan required, Cloud Functions can make outgoing HTTP requests to APIs under control of Google. Typically these are well documented APIs, and are protected from abuse. It's worth noting however, this does not necessarily include anything in the google.com domain. An API has to be specifically whitelisted for use without a payment plan.
If you run into a formal Google-controlled API that is not whitelisted, please file a feature request to have that evaluated.

Storekit - Configure a subscription status URL using Firebase functions

I'm trying to use Firebase functions as our server. Here's the function I'm using. It works fine when I trigger it over HTTP
exports.subscriptions = functions.https.onRequest((request, response) => {
// Send 200 code to the server indicate to that you are done!
response.send(200);
});
Apple Says:
The App Store will deliver JSON objects via an HTTP POST to your
server for the key subscription
But so far I didn't get any notification from Apple server.
Any suggestions would be great?
I checked the logs in the console and it seems that I have started receiving notifications from Apple server.
well you're only saying to apple that you're done, if you add console.log(req.body) to your function it should give you some data in the logs :)

Resources