Closed pramodparanthaman closed 4 years ago
Hi, @pramodparanthaman thanks for raising the issue. Can you show me the code that is failing?
Here is a snippet from a working app:
router.post("/graphql", async (ctx, next) => {
const bearer = ctx.request.header.authorization;
const secret = process.env.SHOPIFY_API_SECRET;
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 settings = await getAppSettings(shop);
const proxy = graphQLProxy({
shop: shop,
password: settings.data.getUser.token || "accessToken",
version: ApiVersion.July20,
});
await proxy(ctx, next);
}
});
router.get("/", async (ctx, next) => {
const shop = getQueryKey(ctx, "shop");
const settings = await getAppSettings(shop);
const token = settings.data.getUser && settings.data.getUser.token;
ctx.state = { shopify: { shop: shop, accessToken: token } };
await verifyToken(ctx, next);
});
Please note that I'm using the following package for isVerified: https://www.npmjs.com/package/shopify-jwt-auth-verify And also the follwing package for the jwt namespace above: https://www.npmjs.com/package/jsonwebtoken
I'm also storing credentials in AWS AppSync, so that's where the settings are being pulled on these lines:
const settings = await getAppSettings(shop);
How are you storing your access token? You mentioned that you are successfully using session tokens, but if you're not storing a valid access token, then the verifyToken method in the koa-shopify-auth-cookieless package will redirect you to /auth.
If you can show me a snippet of how you're trying to accomplish this, it might be helpful.
Nate
I have a live Node/React embedded app on Shopify hosted on Google App Engine that I'm migrating to the new JWT authentication. I plan to use Cloud datastore to persist App Tokens, for now I'm hardcoding the token during development. Almost all my code is identical to the Shopify Node React demo: https://github.com/Shopify/shopify-demo-app-node-react
Here's my server.js code:
require('isomorphic-fetch');
const dotenv = require('dotenv');
const Koa = require('koa');
const next = require('next');
const Router = require('koa-router');
const { createShopifyAuth, verifyToken, getQueryKey, redirectQueryString } = require("koa-shopify-auth-cookieless");
const { graphQLProxy, ApiVersion } = require("koa-shopify-graphql-proxy-cookieless");
const isVerified = require("shopify-jwt-auth-verify")['default']
const jwt = require('jsonwebtoken');
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const { SHOPIFY_API_SECRET_KEY, SHOPIFY_API_KEY, HOST, TEST } = process.env;
app.prepare().then(() => {
const server = new Koa();
const router = new Router();
server.keys = [SHOPIFY_API_SECRET_KEY];
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
scopes: ['read_products', 'read_orders', 'write_inventory'],
accessMode: 'offline',
async afterAuth(ctx) {
const { shop, accessToken } = ctx.state.shopify;
// TODO: Save shop and accessToken to datastore
console.log('ctx.state.shopify: ', ctx.state.shopify);
const redirectQuery = redirectQueryString(ctx);
ctx.redirect(`/?${redirectQuery}`);
},
}),
);
router.post("/graphql", async (ctx, next) => {
const bearer = ctx.request.header.authorization;
const secret = SHOPIFY_API_SECRET_KEY;
const valid = isVerified(bearer, secret);
console.log('isVerified: ', valid); // Displays true
if (valid) {
const token = bearer.split(" ")[1];
const decoded = jwt.decode(token);
console.log('graphQL decoded: ', decoded); // Displays fully decoded JWT payload
const shop = new URL(decoded.dest).host;
// TODO: get accessToken from datastore using shop
const accessToken ='shpat_XXXX';
const proxy = graphQLProxy({
shop: shop,
password: accessToken,
version: ApiVersion.July20,
});
await proxy(ctx, next);
}
});
router.get("/", async (ctx, next) => {
const shop = getQueryKey(ctx, "shop");
// TODO: get accessToken from datastore using shop
const accessToken ='shpat_XXXX';
ctx.state = { shopify: { shop: shop, accessToken: accessToken } };
await verifyToken(ctx, next);
});
router.get('*', async (ctx) => {
const shop = getQueryKey(ctx, "shop");
// TODO: get accessToken from datastore using shop
ctx.state = { shopify: { shop: shop, accessToken: 'shpat_XXXX' } };
const { accessToken } = ctx.state.shopify;
switch(ctx.path) {
default:
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
break;
}
});
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on port ${port}`);
});
});
I setup the GraphQL Apollo client code as shown in the Shopify documentation: https://shopify.dev/tutorials/authenticate-your-app-using-session-tokens
client = new ApolloClient({
link: new HttpLink({
credentials: 'same-origin',
fetch: authenticatedFetch(app),
}),
cache: new InMemoryCache()
});
Hi @pramodparanthaman, thanks for providing the code. I see a few things that should probably be changed:
router.get('*', async (ctx) => {
const shop = getQueryKey(ctx, "shop");
// TODO: get accessToken from datastore using shop
ctx.state = { shopify: { shop: shop, accessToken: 'shpat_XXXX' } };
const { accessToken } = ctx.state.shopify;
switch(ctx.path) {
default:
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
break;
}
});
Try this:
router.get("*", async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
});
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloProvider } from "@apollo/react-hooks";
import { AppProvider } from "@shopify/polaris";
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/en.json";
import createApp from "@shopify/app-bridge";
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import { useRouter } from "next/router";
export default function MyApp({ Component, pageProps }) { const router = useRouter(); const shopOrigin = router.query.shop || "shopOrigin"; const app = createApp({ apiKey: API_KEY, shopOrigin: shopOrigin, forceRedirect: true, }); const link = new createHttpLink({ credentials: "omit", fetch: authenticatedFetch(app), // app: App Bridge instance }); const client = new ApolloClient({ link: link, cache: new InMemoryCache(), });
return (
); }
3. Take a look at the package.json for a working project and make sure you are using similar versions:
"dependencies": { "@apollo/react-hooks": "^4.0.0", "@babel/core": "7.9.0", "@babel/polyfill": "^7.6.0", "@babel/preset-env": "^7.6.2", "@babel/register": "^7.6.2", "@shopify/admin-graphql-api-utilities": "^0.1.1", "@shopify/app-bridge": "^1.26.2", "@shopify/app-bridge-utils": "1.26.2", "@shopify/app-cli-node-generator-helper": "^1.2.1", "@shopify/dates": "^0.4.1", "@shopify/koa-shopify-webhooks": "^2.3.1", "@shopify/network": "^1.5.0", "@shopify/polaris": "^5.2.1", "@shopify/react-network": "^3.5.3", "@zeit/next-css": "^1.0.1", "apollo-cache-inmemory": "^1.6.6", "apollo-client": "^2.6.10", "apollo-link-http": "^1.5.17", "aws-amplify": "^3.2.0", "aws-appsync": "^4.0.0", "dotenv": "^8.2.0", "eslint": "^7.3.1", "file-saver": "^2.0.2", "graphql": "^15.3.0", "isomorphic-fetch": "^2.1.1", "jsonwebtoken": "^8.5.1", "koa": "^2.13.0", "koa-compose": ">=3.0.0 <4.0.0", "koa-router": "^8.0.6", "koa-shopify-auth-cookieless": "^1.0.26", "koa-shopify-graphql-proxy-cookieless": "^1.0.5", "next": "^9.5.3", "next-env": "^1.1.0", "nonce": "^1.0.4", "papaparse": "^5.2.0", "react": "^16.13.1", "react-csv": "^2.0.3", "react-dom": "^16.13.1", "react-papaparse": "^3.8.0", "safe-compare": "^1.1.2", "shopify-jwt-auth-verify": "^1.0.10", "store-js": "^2.0.4" },
Let me know if that helps!
Hi @pramodparanthaman any luck with the redirects? Let me know if you have any further questions.
I’m considering creating a barebones repo with a working example as a reference.
Would this be helpful?
That would be great! Thanks
Ok, I'll go ahead and put that up as soon as I'm able.
In the meantime, I'm going to close this issue.
If you have any other troubles with this package, please reopen this issue or go ahead and open a new one.
Finally got this working.
There were two issues.
I modified your code in shopify-graphql-proxy.js to fix both issues using proxyReqOptDecorator(proxyReqOpts). Please update and also add the latest Shopify API (October20: "2020-10").
Thanks!
await proxy(shop, {
https: true,
parseReqBody: false,
// Setting request header here, not response. That's why we don't use ctx.set()
// proxy middleware will grab this request header
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken,
},
proxyReqOptDecorator(proxyReqOpts) {
delete proxyReqOpts.headers.cookie;
delete proxyReqOpts.headers.Cookie;
delete proxyReqOpts.headers['x-requested-with'];
return proxyReqOpts;
},
proxyReqPathResolver() {
return `https://${shop}${GRAPHQL_PATH_PREFIX}/${version}/graphql.json`;
},
})(
ctx,
/*
We want this middleware to terminate, not fall through to the next in the chain,
but sadly it doesn't support not passing a `next` function. To get around this we
just pass our own dummy `next` that resolves immediately.
*/
noop
);
};
}
@pramodparanthaman, great thanks for the fix. I'll release a new package with the update as soon as I get a chance to test it.
Hi @pramodparanthaman, FYI, the fix has now been incorporated in the latest package.
Also, I created a Shopify Cookieless Demo you can check out here:
Hi Nate,
Thanks for providing 2 very useful modules.
Do you have a working demo app that uses both koa-shopify-auth-cookieless and koa-shopify-graphql-proxy-cookieless? I got the app to install and load using the new Shopify session tokens but I'm really struggling with koa-shopify-graphql-proxy-cookieless, it seems to fail with a CORS error every time I do a graphql query.
POST /graphql 303 See Other
Access to fetch at 'https://XXXX.myshopify.com/admin/auth/login' (redirected from 'https://XX.ngrok.io/graphql') from origin 'https://XX.ngrok.io' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.