Skip to content

Commit

Permalink
Merge pull request #13 from ctrlaltdylan/nextjs-toolbox-v2
Browse files Browse the repository at this point in the history
nextjs toolbox v2
  • Loading branch information
ctrlaltdylan authored Oct 31, 2021
2 parents 82104c6 + c2160c1 commit 7405791
Show file tree
Hide file tree
Showing 9 changed files with 769 additions and 60 deletions.
68 changes: 66 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This example app is using a set of utilities from the [shopify-nextjs-toolbox](h

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 begin the OAuth process if a customer isn't logged in:
In `_app.js` use the `ShopifyAppBridgeProvider` component to check for authentication, and automatically pass the `host` and `shop` parameters to AppBridge if available:

```javascript
// pages/_app.js
Expand All @@ -51,7 +51,27 @@ function MyApp({ Component, pageProps }) {
}
```

The OAuth flow begins at `/api/auth.js`. It will redirect to Shopify's authorization page. No need to do anything else besides export the built in middleware:
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:

```javascript
// 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.

```javascript
// pages/api/auth.js
Expand Down Expand Up @@ -83,6 +103,50 @@ 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`:

```javascript
// 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`:

```javascript
// 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}`.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"nonce": "^1.0.4",
"react": "17.0.1",
"react-dom": "17.0.1",
"shopify-nextjs-toolbox": "file:.yalc/shopify-nextjs-toolbox",
"shopify-nextjs-toolbox": "2.0.0",
"yalc": "^1.0.0-pre.53"
}
}
27 changes: 27 additions & 0 deletions package.json.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "shopify-session-tokens-nextjs",
"version": "0.2.0-beta.7",
"private": false,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@shopify/app-bridge": "^2.0.3",
"@shopify/app-bridge-react": "^2.0.3",
"@shopify/app-bridge-utils": "^2.0.3",
"@shopify/polaris": "^6.6.0",
"axios": "^0.21.0",
"global": "^4.4.0",
"js-cookie": "^2.2.1",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.20",
"next": "10.0.0",
"nonce": "^1.0.4",
"react": "17.0.1",
"react-dom": "17.0.1",
"shopify-nextjs-toolbox": "file:.yalc/shopify-nextjs-toolbox",
"yalc": "^1.0.0-pre.53"
}
}
4 changes: 0 additions & 4 deletions pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import React from "react";
import "../styles/globals.css";
import { ShopifyAppBridgeProvider } from "shopify-nextjs-toolbox";
// import ShopifyAppBridgeProvider from "../components/ShopifyAppBridgeProvider";
import { Provider } from "@shopify/app-bridge-react";
import { useHost } from "shopify-nextjs-toolbox";
import { useShopOrigin } from "shopify-nextjs-toolbox";
import enTranslations from "@shopify/polaris/locales/en.json";
import { AppProvider } from "@shopify/polaris";

Expand Down
11 changes: 10 additions & 1 deletion pages/api/auth.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import { handleAuthStart } from "shopify-nextjs-toolbox";

export default handleAuthStart();
// optional: save the nonce generated within handleAuthStart
// handleAuthStart generates a unique nonce for you, no need to break out a crypto library
// after OAuth is complete, you can compare the nonce generated at this step vs the end for extra security
async function saveNonce({ req, shopName, nonce }) {
// within this function, associate the nonce with the shopName of the merchant logging in you data store of choice
// then within pages/api/auth/callback.js we'll verify the nonce is the same
// not sure how to do this? Just don't pass saveNonce to handleAuthStart
}

export default handleAuthStart({ saveNonce });
10 changes: 9 additions & 1 deletion pages/api/auth/callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ const afterAuth = async (req, res, accessToken) => {
return "/home";
};

export default handleAuthCallback(afterAuth);
const validateNonce = async ({ nonce, req, shopName }) => {
// optional: validate the stored nonce from /pages/api/handleAuthStart against the nonce passed at this step
// the nonce should equal the nonce you stored in saveNonce at handleAuthStart()
// look up your shopName in your data store, and makes sure the nonce matches

return true;
};

export default handleAuthCallback(afterAuth, validateNonce);
2 changes: 1 addition & 1 deletion pages/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React from "react";
import { useOAuth } from "shopify-nextjs-toolbox";

export default function Index() {
Expand Down
10 changes: 10 additions & 0 deletions yalc.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": "v1",
"packages": {
"shopify-nextjs-toolbox": {
"signature": "57e5bb9dd9893ef3076cdedd2ada79c7",
"file": true,
"replaced": "link:../shopify-nextjs-toolbox"
}
}
}
Loading

0 comments on commit 7405791

Please sign in to comment.