Solved

authenticated fetch for new cookie policy error

stheticsoftware
Excursionist
16 0 1

Hey, I followed the post on the new cookieless authentication for Shopify app bridge. I'm trying to make a billing charge when a user takes an action on my app for example a button click should then go to the confirm payment action page. (Sidenote: this is a pretty terrible UX pattern, is there another way to do this like a modal within the embedded app or something? Looks terrible to have to redirect to an entirely different page, confirm the action and then reload the site on the return url) But anyways, I set up the the embedded app, get authentication confirmed etc, and when trying to use a graphql mutation with apollo client, the response is giving the error: "Error: Network error: Unexpected token < in JSON at position 0". I'm assuming this is from an html file being returned instead of JSON. I know that this is a common apollo issue, but I'm hoping for some answers to why the issue is occurring in the first place. Postman requests seem to be working fine, and the main change has been the cookieless authentication using authenticatedFetch from the new app bridge utils package. My set up is a bit unusual with authentication being done through a series of firebase functions calls but the app is being loaded properly in the embedded format. When a shop owner goes to the main shopify app page, the app bridge instance is created and the apollo client is being created as outlined in the new cookieless authentication post.Screen Shot 2020-08-16 at 11.25.16 PM.png

My graphql mutation is a pretty simple create one time charge mutation.

Screen Shot 2020-08-16 at 11.27.48 PM.png

My component uses the outlined tutorial format for the app-bridge-react and apollo wrappings.

Screen Shot 2020-08-16 at 11.30.18 PM.png

The button calls the mutation here:

Screen Shot 2020-08-16 at 11.34.41 PM.png

I cannot for the life of me figure out what went wrong and the fact that the apollo client errors are html and unloggable doesn't help. Can anyone point me in the right direction for what's happening here. Any info on the issues with the general set up, issues/documentation on authenticatedFetch, or even the best ways to investigate html error responses from Apollo would be greatly appreciated. Thanks!

Accepted Solution (1)
Henry_Tao
Shopify Staff
91 28 15

This is an accepted solution.

Hi @stheticsoftware 

 

I think there is a confusion between new `sessionToken` (for cookieless) https://shopify.dev/tools/app-bridge/authentication and `accessToken` from original Shopify auth https://shopify.dev/tutorials/authenticate-with-oauth. They are two different token. 

 

- The `accessToken` is used to perform graphql call on your server. This is obtained during app installation.

- The `sessionToken` is only meant for validating the request coming from your app loading inside Shopify. It cannot be used as `X-Shopify-Access-Token`. 

 

Thanks, 

 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

View solution in original post

Replies 24 (24)

Henry_Tao
Shopify Staff
91 28 15

Hi @stheticsoftware 

 

Can I have your appId? "Unexpected token < in JSON at position 0" This error could happen when embedded app uses App Bridge but the host is still EASDK.

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Hey thanks Henry, my AppID is: eeecdd09e49df1307834481f5d10dbf8

Henry_Tao
Shopify Staff
91 28 15

Hi @stheticsoftware 

 

The `authenticatedFetch` will retrieve `sessionToken` and put it to header `Authorization: Bearer session-token-from-authenticated-fetch`. You need to use retrieve bearer token and validate it in your server. Are you doing it? 

 

"Postman requests seem to be working fine"

=> What's this request? 

 

"My set up is a bit unusual with authentication being done through a series of firebase functions calls but the app is being loaded properly in the embedded format."

=> Did you retrieve bearer token in Firebase functions? How do you perform graphql mutation in the server? What token did you use? 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Thanks for the info again. Im retrieving and validating the shopify access token and setting headers as "X-Shopify-Access-Token": shpat_xxxxxxxx. I take it that this is not the proper access token to retrieve/store? I know the JWT is a different format, but is that retrieved in a similar way to the POST request to "https://${shop}/admin/oauth/access_token"? As for the Postman request thats just to make sure the api calls are formatted correctly/returning proper data. www.postman.com . I'm not sure if I'm properly retrieving/using the bearer token, as I'm using the access token provided by the POST request to "https://${shop}/admin/oauth/access_token" and then signing calls with the "X-Shopify-Access-Token" header. Here's an examplpe of a working api call from the server:

 Screen Shot 2020-08-17 at 3.27.53 PM.pngScreen Shot 2020-08-17 at 3.28.05 PM.png

 

Henry_Tao
Shopify Staff
91 28 15

This is an accepted solution.

Hi @stheticsoftware 

 

I think there is a confusion between new `sessionToken` (for cookieless) https://shopify.dev/tools/app-bridge/authentication and `accessToken` from original Shopify auth https://shopify.dev/tutorials/authenticate-with-oauth. They are two different token. 

 

- The `accessToken` is used to perform graphql call on your server. This is obtained during app installation.

- The `sessionToken` is only meant for validating the request coming from your app loading inside Shopify. It cannot be used as `X-Shopify-Access-Token`. 

 

Thanks, 

 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Perfect, thanks! So as I understand, we don't necessarily need to use the sessionToken or cookies as long as we have other methods of validating communication between front-end and server. And the accessToken on the server will continue to work as expected?

Henry_Tao
Shopify Staff
91 28 15

we don't necessarily need to use the sessionToken or cookies as long as we have other methods of validating communication between front-end and server.

 

Correct. However, I am curious how you validate the communication. Would you mind to share it as well? Thanks.

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Sure, during the initial authentication process, we have the firebase functions also create a custom firebase authentication token that is passed back to the client. The client is signed in with that custom token. All communication after that is done with firebase function triggers to the database (ie. the client updates their database document which has rules to only allow the user to write to their own document, then the functions listen for updates to the documents and make the api calls). The custom token changes with each sign on as well. We're considering adding in some more security at some point, perhaps encrypting the tokens in transit and having the individual's secret key stored in their personal database document or something. Please let me know if you have any other suggestions or flaws you can think of!

Henry_Tao
Shopify Staff
91 28 15

It sounds to me that you are using Login with Google for the initial authentication. If so, I would suggest to enable `Block third-party cookies` in the browser and test the flow again. 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Yep, it all works with cookies blocked. It's not actually google login, its called firebase custom tokens https://firebase.google.com/docs/auth/admin/create-custom-tokens its a pretty cool authentication method. The functions/server generates an access token that lives for an hour. They're actually still JWT like the Shopify session tokens. I'm curious how you guys recommend passing JWT to the client side. From what I've seen there's still a bit of a debate going on between passing with web/session storage vs cookies. 

stheticsoftware
Excursionist
16 0 1

Here's some of the code for anyone else doing trying to do auth/api calls in this way. I'd love some feedback on the security of this!

On the app entry point link, it goes to my react app which has a route method to call this firebase function:askForPermissions

Screen Shot 2020-08-18 at 11.30.26 AM.png

After the ask for permissions completes, it's redirected to confirm install here which is where we create the firebase token and create the user database document if there isn't one already.

My main security concern here is passing in the firebase take in the url.

Screen Shot 2020-08-18 at 11.31.45 AM.pngScreen Shot 2020-08-18 at 11.32.59 AM.pngScreen Shot 2020-08-18 at 11.33.32 AM.png

Then on the front end, we retrieve the firebaseToken and sign in the shop user with that.

Screen Shot 2020-08-18 at 11.43.01 AM.png

All further api calls are made from the firebase functions/server when a user writes to their database document. The database rules only allow for a person to write to the database document associated with the user they are signed in as.

Screen Shot 2020-08-18 at 11.39.25 AM.png

Hope this can help some people!

Henry_Tao
Shopify Staff
91 28 15

Hi @stheticsoftware 

IMO, I think the way you are using firebase custom token is insecure. The problem is that untrusted clients could receive custom token from the server. There are two things we need to verify:
- 1st: Shopify and the client. 
- 2nd: the client and the server. 

I could be wrong in the way your app is setup. However, I only see two options to clarify your app coming from Shopify: cookies or ask Shopify at runtime through new sessionToken. The firebase custom token is only for verifying client and server connection. 

 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

Henry_Tao
Shopify Staff
91 28 15

Hi @stheticsoftware 

Because you just provide extra information. I think your setup is fine. However, the UX is not nice because the app performs auth redirect every time merchants open it. Both cookies and new sessionToken are supposed to deal with UX of subsequent app opening request. 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Hey @Henry_Tao I really appreciate the feedback. From what I understand from the shopify oauth flow, doesn't the extra information like the HMAC, nonce, and authorization code validate that the requests are coming from an authorized shopify client? I'm definitely with you on the UX patterns though haha, in my initial post I also mentioned this, it's a pretty bad flow especially when making one time app charge redirects. We bill a calculated variable charge each time a user takes a certain action on the site and have to redirect to confirm each time and lose place in the app which is pretty rough.

Henry_Tao
Shopify Staff
91 28 15

Hi @stheticsoftware 

You are right about HMAC. It can be used to verify the request coming from Shopify. However, HMAC has a couple of limitation:
- It is only available in the first request. A single page app might have problem with validating subsequent requests.
- It is in request query param which can be hijacked very easy. You often need to combine with timestamp to invalidate the request.

The new sessionToken is a better option in term of authentication. 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Awesome thanks, yes this was the original meaning of my post. I couldn't get the authenticatedFetch to work at all, perhaps this new sessionToken would help. Any idea of how to implement this with my set up? Since I'm not using the app gem, the documentation says to obtain the sessionToken manually here https://shopify.dev/tools/app-bridge/authentication but I'm unclear where exactly the session token is coming in, where/how to decode it since the appBridge set up was not working.The docs mentioned calling an appbridge action to get the sessionToken but I couldn't find the specific docs on that.

stheticsoftware
Excursionist
16 0 1

Just for some more info I tried doing this: Screen Shot 2020-08-18 at 12.37.27 PM.png but couldn't get the promise to resolve so I gave up.

Henry_Tao
Shopify Staff
91 28 15

Hi @stheticsoftware 

It's a promise, you cannot do `console.log` directly. Try this:

 

const sessionToken = getSessionToken(window.app);
sessionToken
  .then((token) => {
    console.log('This is my token', token);
  })
  .catch((error) => {
    console.log('Something wrong happens', error);
  });

 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Ah yes, that was a placeholder, I originally used an await before, but either way I get this error?

 Screen Shot 2020-08-18 at 12.55.01 PM.png

When I check that, it's looking for the appbridge instance which it says is undefined, however when I log the app instance its properly returning the app object with its context etc.

Screen Shot 2020-08-18 at 12.57.47 PM.png

stheticsoftware
Excursionist
16 0 1

Once again, I really appreciate the help so far, I realize the thread has gone on a bit of a tangent

Henry_Tao
Shopify Staff
91 28 15

Can you wrap the `getSessionToken` inside `if (window.app) { ... }` or moving it around to make sure there is no async call?

I suspect you have some kind of async code causing first `window.app` passing to `getSessionToken` is undefined. 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

stheticsoftware
Excursionist
16 0 1

Perfect thanks! Yep there ended up being two issues, there was the async stuff and then the getSessionToken from  window.app had to be from just app in my case. Using this format worked out:

if(app){
const sessionTok = getSessionToken(app);
console.log("Current App: ", app);
sessionTok
.then((token) => {
console.log('This is my token', token);
})
.catch((error) => {
console.log('Something wrong happens', error);
});
}

 

Thanks for all the help!

Henry_Tao
Shopify Staff
91 28 15

Hi @stheticsoftware 

Please check below steps: 

1. The authenticatedFetch retrieves sessionToken and appends it to request header in the form of `Authorization: "Bearer " + sessionToken`. Check https://cdn.jsdelivr.net/npm/@shopify/app-bridge-utils@1.26.2/utilities/session-token/authenticated-... and https://shopify.dev/tools/app-bridge/authentication#when-youre-done

2. When the request hits your server, you need to look for Authorization header, extract `sessionToken`, and validate it. Check https://shopify.dev/tools/app-bridge/authentication#obtain-session-details-manually and  https://shopify.dev/tools/app-bridge/authentication#verify-the-signature 

3. If you don't use Apollo, you can use `getSessionToken` promise in `app-bridge-utils` utils instead. It will returns a sessionToken whenever you call it. Check https://shopify.dev/tools/app-bridge/authentication#use-session-tokens-with-axios 

I hope it helps. 

Henry | Social Care @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit the Shopify Help Center or the Shopify Blog

cormacncheese
Shopify Partner
11 0 0

Could you explain this error a bit more?

I recently upgraded @Shopify/app-bridge-react and @Shopify/app-bridge-utils to 2.02 and I've been receiving the following error on my queries: 

Error: Network error: Unexpected token N in JSON at position 0

Screen Shot 2021-06-08 at 7.23.59 PM.png

Screen Shot 2021-06-08 at 7.24.20 PM.png

Screen Shot 2021-06-08 at 7.24.40 PM.png

I've tested my query in the Shopify GraphiQL App and it works fine there. Here is my query:

export const GET_NEXT_PRODUCTS = gql`
     query ($first: Int, $cursor: String) {
           products(first: $first, after: $cursor) {
               pageInfo {
                     hasNextPage
                     hasPreviousPage
               }
              edges {
                   cursor
                   node {
                         id
                         title
                         handle
                         description
                         images(first: 1) {
                                edges {
                                     node {
                                         originalSrc
                                         altText
                                     }
                                }
                           }
                   tiktok: metafield (
                        namespace: "tiktok"
                        key: "tiktok") {
                             value
                              id
                        }
                  }
            }
     }
}
`;

 

And server.js:

router.post("/graphql", async (ctx, next) => {
     const bearer = ctx.request.header.authorization;
     const secret = process.env.SHOPIFY_API_PRIVATE_KEY;
     const valid = isVerified(bearer, secret);
     if (valid) {
          const token = bearer.split(" ")[1];
          const decoded = jwt.decode(token);
          const shop = new URL(decoded.dest).host;
          const accessToken = store.get('token');

          if (shop) {
               const proxy = graphQLProxy({
               shop: shop,
               password: accessToken,
               version: ApiVersion.April21,
          });
     await proxy(ctx, next);
     } else {
          ctx.res.statusCode = 403;
     }
     }
});