Giter Club home page Giter Club logo

shopify-session-tokens-nextjs's Introduction

This is an example Next.js project consuming Shopify App Bridge Session Tokens for authentication using utilities from shopify-nextjs-toolbox

Getting Started

First, set your Shopify App's public & private keys in .env.local

SHOPIFY_API_PUBLIC_KEY='your public api key from the Shopify app dashboard here'
SHOPIFY_API_PRIVATE_KEY='your private api key from the Shopify app dashboard here'
NEXT_PUBLIC_SHOPIFY_API_PUBLIC_KEY='same value as SHOPIFY_API_PUBLIC_KEY, this will expose your public key to the frontend'
SHOPIFY_AUTH_CALLBACK_URL='<your-sub-domain>.ngrok.io/api/auth/callback'
SHOPIFY_AUTH_SCOPES='read_customers,write_customers' # a comma separated list of Shopify Auth scopes your app requires to function
HOME_PATH = '/home' # or wherever you'd like the user to be sent to after successfully authenticating

Then make sure your app is configured to use <your-sub-domain>.ngrok.io as the entry point.

Second, start up ngrok and configure it to use localhost:3000.

Then, run the development server:

npm run dev
# or
yarn dev

How it works

This example app is using a set of utilities from the shopify-nextjs-toolbox to handle Shopify's OAuth handshake and cookie-less session token generation & verification.

OAuth Handshake

When a customer opens your app, they will be directed to your app's defined homepage in your Shopify App settings.

In _app.js use the ShopifyAppBridgeProvider component to check for authentication, and automatically pass the host and shop parameters to AppBridge if available:

// pages/_app.js

function MyApp({ Component, pageProps }) {
  // The ShopifyAppBridgeProvider abstracts starting the OAuth process
  //   it will automatically redirect unauthenticated users to your `/api/auth.js` route
  return (
    <ShopifyAppBridgeProvider Component={Component} pageProps={pageProps}>
      <AppProvider i18n={enTranslations}>
        <Component {...pageProps} />
      </AppProvider>
    </ShopifyAppBridgeProvider>
  );
}

Next create a pages/index.js that will act as the entry point for unauthenticated merchants.

useOAuth calls your pages/api/auth.js route which generates the URL needed to redirect to start OAuth.

Under the hood useOAuth will redirect to this URl as soon as it's available to start the handshake:

// pages/index.js

import React from "react";
import { useOAuth } from "shopify-nextjs-toolbox";

export default function Index() {
  useOAuth();

  // replace this with your jazzy loading icon animation
  return <>Loading...</>;
}

The OAuth flow begins at /api/auth.js. It will generate the URL to the merchant's Shopify dashboard route to give back to the frontend useOAuth hook.

// pages/api/auth.js

import { handleAuthStart } from "shopify-nextjs-toolbox";

export default handleAuthStart;

After the user accepts your app's scopes and terms, they will be redirected from Shopify to /api/auth/callback.js.

That route will then verify the signature of the request and retrieve the merchant's Shopify access token.

The afterAuth function is called after the access token is successfully retrieved. Create your own afterAuth to store the shop's access token which is passed as the third argument sessionToken:

// pages/api/auth/callback.js
import { handleAuthCallback } from "shopify-nextjs-toolbox";

const afterAuth = async (req, res, accessToken) => {
  // save accessToken with the shop
  db.collection("shop").insertOne({ name: req.query.shop, accessToken });

  // redirect is handled by handleAuthCallback, no need to res.send() or res.redirect() here.
};

export default handleAuthCallback(afterAuth);

Now that the merchant's OAuth handshake is complete, the customer is finally redirected to /pages/home.js, or whichever path you provide in process.env.HOME_PATH. This route is an internal route. Meaning, it can assume that the Shopify AppBridge has a valid shopDomain query parameter, and the merchant is authenticated by OAuth.

Optional: nonce storage & validation

During OAuth you can (and should) store the a unique nonce to verify Shopify's identity during the callback.

We take care of generating this unqiue nonce, but we leave it up to you to store it in your database of choice during startAuth:

// pages/api/auth.js

import { handleAuthStart } from "shopify-nextjs-toolbox";

const saveNonce = async (req, shopName, nonce) => {
  // shopify-nextjs-toolbox does the work of generating a secure unique nonce
  //   for better security, associate this nonce with the shop
  //
  // Example:
  // await db.connect().collection('nonces').insertOne({ shopName, nonce });
};

export default handleAuthStart({ saveNonce });

Then, after the merchant accepts your scopes you can validate the nonce returned by Shopify in the handleAuthCallback:

// pages/api/auth/callback.js

import { handleAuthCallback } from "shopify-nextjs-toolbox";

const validateNonce = async (nonce, req) => {
  // retrieve the nonce associated with the current shop from OAuth
  // validate the nonce passed into this argument matches that nonce
};

const afterAuth = async (req, res, tokenData) => {
  const shop = req.query.shop;
  const accessToken = tokenData.access_token;

  // save the accessToken with the shop in your database to interact with the Shopify Admin API
};

export default handleAuthCallback(afterAuth, { options: { validateNonce } });

App Bridge Session Token Retrieval

After the handshake is complete, in the _app.js the App Bridge is instantiated and the session token is retrieved. The host is transferred to your app by Shopify through the query param host={host}.

The pages/home.js is not rendered until the session token is available for consumption.

Once the page loads, then an HTTP request with the session token is sent to /api/verify-token.js where it's decoded and validated with your app's private key.

TODO

Learn More

To learn more about Next.js, take a look at the following resources:

You can check out the Next.js GitHub repository - your feedback and contributions are welcome!

shopify-session-tokens-nextjs's People

Contributors

cam5 avatar ctrlaltdylan avatar darcy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shopify-session-tokens-nextjs's Issues

Startup errors after pulling in @shopify/shopify-api

Hi, I am new to Shopify app development. Have used your template for your new app and it works great. However, I understand that every app should do this on start:

Shopify.Context.initialize({
    API_KEY: ..,
    API_SECRET_KEY: ..,
    SCOPES: ..,
    HOST_NAME: ..,
    API_VERSION: '2022-07',
    IS_EMBEDDED_APP: true,
    SESSION_STORAGE: ...,
  });

What would be a good place to do the initialize call? In order to use the Shopify object, I added @shopify/shopify-api to the project via npm. I have checked the default template at: https://github.com/Shopify/shopify-app-template-node/blob/main/package.json#L22-L26 and they are using version 3.0.0 of @shopify/shopify-api.

However, now that the dependency has been added, it suddenly fails to start any longer:

npm run dev

> [email protected] dev
> next dev

Browserslist: caniuse-lite is outdated. Please run:
npx browserslist@latest --update-db
Loaded env from /Users/username/dev/projectname/.env.local
ready - started server on http://localhost:3000
error - ./node_modules/@mapbox/node-pre-gyp/lib/testbinary.js:9:0
Module not found: Can't resolve 'child_process'
null

I have tried Node 12, 14 and 16 with no luck. Any ideas?

Error on with handleAuthStart + saveNonce

I'm having a very similar issue to ctrlaltdylan/shopify-nextjs-toolbox#13, using the latest version of this repo. I have written a simple saveNonce function and am exporting it with export default handleAuthStart({ saveNonce }); at the bottom of api/auth.js

I then try to install it into the shopify dev store and it redirects to a strange url (as described in the issue above):
https://<store>.myshopify.com/admin/apps/<appid>/?hmac=<hmac>&host=<hostid>&shop=<store>.myshopify.com&timestamp=1652701151 (senitive parts omitted in <>)

Whereby shopify give an error on the page

There’s no page at this address
Check the URL and try again, or use the search bar to find what you need.

Checking the vercel function logs, I can see clearly the POST requests to /api/auth were successful, and logging shows the nonce was added to the database correctly.

What is strange is that removing the saveNonce, the app functions perfectly. So the question is - why is saveNonce causing it to do this?

Any ideas why? I've looked at the code for handleAuthStart and am stumped as to why it is doing this.

Side note - great starter app. First one so far that has actually worked and not super out of date ❤️

Empty auth callback handler

I'm playing around with this example repo because the code generated by the shopify-cli does not work for me.
I found the empty auth callback handler with a comment save accessToken with the shop. What would I use this token for and where would I store it preferably?

Another question: The code from the shopify-cli (using a koa server) stored app installations and used that for redirecting the user to the authentication endpoint if a new installation was encountered. Do I need to implement this as well when using the toolbox?

Best way to handle redirect to subscriptionURL

@ctrlaltdylan thanks for the frame! Love it!

q.q. during callback if I want to redirect to confirmationUrl what would be the best way? This is what I tried

    const afterAuth = async (req, res, accessToken) => {
    const { shop } = req.query;
    const { access_token } = accessToken;
    // console.log(`Shop: ${shop}; AccessToken: ${JSON.stringify(accessToken)}`);

    const installResponse = await installApp(
        access_token,
        shop,
        APP_HOST,
        SHOPIFY_API_VERSION,
    );

    // present user with billing options
    if (installResponse.status === 201) {
        await getSubscriptionUrl(res, access_token, shop, APP_HOST, SHOPIFY_API_VERSION);
    }
};

export default handleAuthCallback(afterAuth);

from getSubscriptionUrl I return

   ...
   ...
   const response = await fetch(`https://${shop}/admin/api/${apiVersion}/graphql.json`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-Shopify-Access-Token': accessToken,
        },
        body: query,
    });

    const responseJson = await response.json();
    const confirmationUrl = responseJson.data.appSubscriptionCreate.confirmationUrl;

    return res.redirect(confirmationUrl);

and I am getting this issue

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:558:11)
    at setHeadersFromObject (/Users/app/node_modules/next/dist/compiled/compression/index.js:1:155900)
    at ServerResponse.setWriteHeadHeaders (/Users/app/node_modules/next/dist/compiled/compression/index.js:1:156134)
    at ServerResponse.writeHead (/Users/app/node_modules/next/dist/compiled/compression/index.js:1:155341)
    at redirect (/Users/app/node_modules/next/dist/next-server/server/api-utils.js:27:357)
    at ServerResponse.apiRes.redirect (/Users/app/node_modules/next/dist/next-server/server/api-utils.js:6:354)
    at _callee$ (/Users/app/node_modules/shopify-nextjs-toolbox/lib/middleware/oauth/handleAuthCallback.js:58:19)
    at tryCatch (/Users/app/node_modules/regenerator-runtime/runtime.js:63:40)
    at Generator.invoke [as _invoke] (/Users/app/node_modules/regenerator-runtime/runtime.js:293:22)
    at Generator.next (/Users/app/node_modules/regenerator-runtime/runtime.js:118:21)
    at asyncGeneratorStep (/Users/app/node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:24)
    at _next (/Users/app/node_modules/@babel/runtime/helpers/asyncToGenerator.js:25:9)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:93:5) {
  code: 'ERR_HTTP_HEADERS_SENT'
}
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:558:11)

TypeError. pages is undefined

When opening my app I often see this screen flash by for a second before the app redirects. It then seems to work as expected. This is when running npm run dev on my local machine.

2021-04-23_12-15

Then when actually running this in production on Vercel I'm always met by this screen on a cold boot. If vercel is warm, like if I try one more time, it works as expected. I am not redirected when this screen shows up however, the user is stranded here. This got my app rejected by Shopify yesterday since they couldnt install it. I wonder if this is related somehow? Or does anybody know anything about why this shows up? I've been checking the source code for shopify-nextjs-toolbox but can't find anything.
unnamed

Blank Screen

Launching the project on my localhost with npm run dev leaves me with a blank screen.

If I open the ngrok tunnel through the Shopify Partners app, it correctly shows the page for a second then refreshes to this error.
image

Error at the start of OAuth flow

Hello @ctrlaltdylan ,
First of all thanks so much for this awesome repo, using Koa custom server doesn't really make a huge sense with next js.

second, I have implemented the steps in read me and trying to start the auth flow by visiting my app url with shop param like https://app.ahmed.wiki/?shop=my-shop.myshopify.com i get the below error when call the/auth api

{"message":"Unauthorized: shop missing from query string."}

looking into the network tab found the following
Screen Shot 2021-11-01 at 11 00 42 PM

so I had to start the flow by visiting https://app.ahmed.wiki/?a=1&shop=my-shop.myshopify.com and adding a=1 as it seems the library adds ? to the first query param and the rest of params being read correctly. After doing this everything works correctly.

any ideas on how to get rid of my hack a=1 param?

Thanks so much

client undefined?

Hi :)

I'm getting api_key undefined when I try to install the app. Although I'm pretty sure i have the .env.local setup?
I have to manually put the client id value (api key) to make the setup work.
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.