FROM CACHE - en_header

Authenticating Shopify app with Firebase

ghesp
Tourist
7 0 2

I've been following the Shopify Node/React tutorial and everything is understandable.

For testing, they're storing product ID's in a cookie, however obviously for a production app, this needs to have some sort of central database for which I've chosen Firebase Firestore as I'm fairly familiar with it.

Where I'm getting caught up though is understanding how I would hook the Shopify Auth into the Firebase Auth.  Normally I'd be using Username/Password which would create a session for the user, which is then used in Firebase calls and handles the authentication.

For the Shopify App though, there is no user sign in, so I somehow need a way to authenticate this with Firebase.

Has anyone done this before, and if so, do you have any pointers?

Replies 13 (13)
policenauts1
Trailblazer
174 13 38

 

@ghesp I'm kind of facing the same question and was planning to use Firebase Realtime Database for my database since I'm familiar with it. My thought (and someone please correct me if this is a poor approach): Wouldn't the authentication happen all on the Shopify side (by verifying hmac, etc.) as outlined toward the bottom here: https://shopify.dev/tutorials/authenticate-with-oauth?

 

Then once my app verifies the request is authentic, I was planning to just set up my database with each node being individual shopOrigins, and processing all requests to the database on the server side myself using my Firebase id and secret (global access) which only my server code has access to - so in other words, each user would have directories within my database, but they wouldn't actually use the Firebase auth feature and instead I'd be reading/writing to the database from my server based on their respective shopOrigin. 

 

So put differently, as long as you're planning to use Firebase simply for your back-end but the user doesn't need to access another front-end separate from your actual Shopify app (which would then necessitate Firebase auth), then you don't need to use the Firebase Auth feature and instead just map the database nodes to each merchant for data read/write - is that right? 

ghesp
Tourist
7 0 2

I've been following the tutorial that uses Koa.

Within the createShopifyAuth(), I was doing this:

async afterAuth(ctx) {
....
await db.collection('shops').doc(shop).create({
name: shop,
accessToken
});
},

 The problem isn't how my backend communicates with Shopify, but how do I authenticate the user with my application when Shopify only gives you the store URL from the afterAuth function.

I could pass the cookie to the backend, but anyone could modify that and just get data from another store.  The backend needs to somehow validate that the store the request comes from is the store it says it is before my app pulls their Firebase data.

policenauts1
Trailblazer
174 13 38

Right, I'm grappling with the same questions as I build my first app. Did you read the link I sent regarding hmac and scroll to the bottom? Apparently they give you hmac on each request or redirect and you can use that to verify the validity of the request. 

ghesp
Tourist
7 0 2
policenauts1
Trailblazer
174 13 38

Ah hmm, I'm not using koa in my case so I don't know. But when I google "shopify koa hmac" a few threads come up, so I think others have done it? 

ghesp
Tourist
7 0 2

Looking into it more, verifying the HMAC signature Shopify sends in their requests makes sure the request came from Shopify. I'm not sure this would help in verifying the store it comes from is the store it says it is

policenauts1
Trailblazer
174 13 38

If the hmac passes (which also checks hostname), at that point wouldn't it necessarily mean it's that merchant logged in to that Shopify account since the request is coming from Shopify? I'm also asking this question to help my own understanding.

ghesp
Tourist
7 0 2

Hmmm I'm not sure there. I though the hmac validation was just to ensure the requests actually came from a Shopify by matching against your API Secret. 

I think the way this maybe needs to work, is that any request to do something against the app database, needs to run a query against Shopify using the access token and shop name cookie to validate they match?

Can anyone confirm?

ghesp
Tourist
7 0 2

Think I might have understood this now, but would like someone to confirm

The koa-shopify-auth package has a verifyToken export that states 

Returns a middleware to verify requests before letting them further in the chain.

Looking into this more, it seems this uses the accessToken to check it does match the store.  

https://github.com/Shopify/quilt/blob/master/packages/koa-shopify-auth/src/verify-request/verify-tok...

 

If we then look at this project: https://github.com/vetalas/shopify-app-firebase/blob/master/src/functions/index.js

They use Koa-Router, and on their GET routes, they use the same verifyRequest function as middleware.


I think that the way to handle this is to use the verifyRequest function (or just make a call to the admin api).  If that succeeds, then you know the store making the request is successfully authenticated, and isn't "faking" a data request.

 

 

ghesp
Tourist
7 0 2

Having thought about this, i think this might be relatively simple now

In your server.js file, you need to use the admin-sdk to create a customToken and pass this back to the UI.  As an example, I'm just sticking the shop data in a collection called shop, with the document ID being the shop name from the session

async afterAuth(ctx) {
const {shop, accessToken} = ctx.session;

await getSubscriptionUrl(ctx, accessToken, shop);

const customToken = await admin.auth().createCustomToken(shop)

ctx.cookies.set('shopOrigin', shop, {
httpOnly: false,
secure: true,
sameSite: 'none'
});

ctx.cookies.set('token', customToken, {
httpOnly: false,
secure: true,
sameSite: 'none'
});

await db.collection('shops').doc(shop).set({
domain: shop,
token: cryptr.encrypt(accessToken),
}, {merge: true});
}, 

   

Then in the web app index, create your firebase-web.js file for the instance:

import firebase from 'firebase';

const instance = firebase.initializeApp({
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
});

export default instance;

 

Then in your index.js file, 

import firebase from "../helpers/firebase-web";

and use the useEffect hook to check to see if the user is logged into Firebase, if not, log them in immediately

useEffect(() => {
firebase.auth().onAuthStateChanged(user => {
if(user) {
setSignedIn(true)
} else {
firebase.auth().signInWithCustomToken(Cookies.get('token'));
}
})
},[])

Then just setup your Firestore Rules

 match /shops/{shop}/{documents=**} {
    allow read, write: if request.auth != null && request.auth.uid == shop
}
Now you're free to make calls to Firebase from the web app as usual, using the uid as the value to restrict access

If anyone see's a flaw in this, please let me know 🙂
mirnaaa
New Member
8 0 0

The oAuth Shopify provides is for public apps only. I have a private app that I've built for subscriptions. Would Firebase work with this setup? 

XscapeCo
New Member
2 0 1

Would you happen to have an example of a full version of the server.js file that shows how the token is put into the firebase db?

devHam
Tourist
8 0 1

I just want to say a big thank you for sharing the above solution. This helped me a great deal to get my client-side access control setup for my Shopify app.

I'm just wondering..

I have a development and a production firebase project, with different firebase configs. I would love to be able to use environment variables to determine which project config to use for client-side Firebase initialisation.

On the server-side, I can obviously use process.env variables to grab config values from my .env file. Is it possible to do this from the client-side?

So, something like..

import firebase from 'firebase';

const instance = firebase.initializeApp({
apiKey: process.env.APIKEY, // where I could have both a dev and prod environment key defined in my .env files
...
});

export default instance;

 Thanks!