Re: validate Authenticated Session

How can I fix the failure in validating authenticated session in Shopify CLI?

Ninad27
Shopify Partner
6 0 0

Using shopify cli I have created boilerplate code to build app using React.

on React UI we are calling API to get data - 

const {
data,
refetch: refetchProductCount,
isLoading: isLoadingCount,
isRefetching: isRefetchingCount,
} = useAppQuery({
url: "/api/products/count",
reactQueryOptions: {
onSuccess: () => {
console.log("success...");
},
},
});
 
 
but on server side it is failing in shopify.validateAuthenticatedSession().
I have already done setup for redis session storage and it is working,
want to know what is missing here and how I can get data from graphql query inside React UI?
Replies 9 (9)

okur90
Shopify Partner
126 20 19

How did you set up your server-side code, especially the part where you're using `shopify.validateAuthenticatedSession()`?

Code Slingin, Pixel Wranglin - Laugh it up at onlinex.com.au
Ninad27
Shopify Partner
6 0 0
import { join } from "path";
import { readFileSync } from "fs";
import express from "express";
import serveStatic from "serve-static"

 

import shopify from "./shopify.js";
import GDPRWebhookHandlers from "./gdpr.js";

 

const PORT = parseInt(process.env.BACKEND_PORT || process.env.PORT, 10);

 

const STATIC_PATH =
process.env.NODE_ENV === "production"
? `${process.cwd()}/frontend/dist`
: `${process.cwd()}/frontend/`;

 

const app = express();
 
app.get(shopify.config.auth.path, shopify.auth.begin());
app.get(
shopify.config.auth.callbackPath,
shopify.auth.callback(),
shopify.redirectToShopifyOrAppRoot()
);
app.post(
shopify.config.webhooks.path,
shopify.processWebhooks({ webhookHandlers: GDPRWebhookHandlers })
);
app.use("/api/*", shopify.validateAuthenticatedSession());

 

app.use(express.json());

 

app.get("/api/products/count", async (req, res) => {
console.log("inside /api/products/count"); // 1. trying to reach here to fetch data from graphql and pass it to UI.

 

// const countData = await shopify.api.rest.Product.count({
// session: res.locals.shopify.session, // 2. also not sure what this is
// });
res.status(200).send(10);
});

 

app.use(shopify.cspHeaders());
app.use(serveStatic(STATIC_PATH, { index: false }));

 

app.use("/*", shopify.ensureInstalledOnShop(), async (_req, res, _next) => {
return res
.status(200)
.set("Content-Type", "text/html")
.send(readFileSync(join(STATIC_PATH, "index.html")));
});

 

console.log("App Running on ", PORT);
app.listen(PORT);
 
 
 
I have created above code using shopify cli.
I have setup redis storage, I can see key's inside redis so access token is getting stored.
Also from UI side wherever I call /api/products/count API, it does have valid JWT token in Authorization key of header.
okur90
Shopify Partner
126 20 19

First, make sure you've got the right environment variables set up for redis session storage. Your `.env` file should have these variables.

 

SHOPIFY_APP_SECRET=<your_app_secret>
SHOPIFY_APP_API_KEY=<your_app_api_key>
SHOPIFY_APP_API_SECRET_KEY=<your_app_api_secret_key>
SHOPIFY_APP_API_SCOPES=<your_app_scopes>
SHOPIFY_APP_IS_EMBEDDED_APP=true
SHOPIFY_APP_HOST=<your_app_host> /* Make sure you set the `SHOPIFY_APP_HOST` to your app's hostname, this is important for the authentication process to work correctly.*/
SHOPIFY_APP_BACKEND_PORT=<your_app_backend_port>
SHOPIFY_APP_FRONTEND_PORT=<your_app_frontend_port>
REDIS_HOST=<your_redis_host>
REDIS_PORT=<your_redis_port>
REDIS_PASSWORD=<your_redis_password>

 

Your API request from the UI side already has a valid JWT token in the `Authorization` header, so let's update the server-side code to use this token instead of session storage.

replace this

   app.use("/api/*", shopify.validateAuthenticatedSession());

with this

   app.use("/api/*", shopify.verifyToken());

 

lastly update your `/api/products/count` endpoint to use the GraphQL API for fetching the product count. Replace the commented-out code with

 

  const { data } = await shopify.api.graphql.query({
     session: res.locals.shopify.session,
     query: `{
       productsCount: products(first: 0) {
         count
       }
     }`,
   });

   res.status(200).send(data.productsCount.count);

 

After you've made these changes, your app should be able to grab the data from the GraphQL API just fine and display it on your React UI.

 

 

Code Slingin, Pixel Wranglin - Laugh it up at onlinex.com.au
Ninad27
Shopify Partner
6 0 0

Thank you for your replay with explanation.
I tried and realise that I don’t have
shopify.verifyToken() method available.

 

    "@shopify/shopify-app-express": "^2.0.0",

 

And I am using shopifyApp method from that package like this -

 

const shopify = shopifyApp({

  api: {

    apiVersion: LATEST_API_VERSION,

    restResources,

    billing: undefined, // or replace with billingConfig above to enable example billing

  },

  auth: {

    path: "/api/auth",

    callbackPath: "/api/auth/callback",

  },

  webhooks: {

    path: "/api/webhooks",

  },

  // This should be replaced with your preferred storage strategy

  sessionStorage: new RedisSessionStorage(

    `redis://${Config.REDIS_HOST_IP}:${Config.REDIS_HOST_PORT}`

  ),

});

export default shopify;

okur90
Shopify Partner
126 20 19

You can create a utility function to decode and verify the JWT token.

 

 

// utils/decodeJWT.js
import jwt from "jsonwebtoken";

const decodeJWT = (token) => {
  try {
    const decoded = jwt.verify(token, process.env.SHOPIFY_APP_API_SECRET_KEY);
    return decoded;
  } catch (error) {
    return null;
  }
};

export default decodeJWT;

 

 

and then create a custom middleware to verify the JWT token

 

 

// middleware/verifyToken.js
import decodeJWT from "../utils/decodeJWT.js";

const verifyToken = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  const token = authHeader.split(" ")[1];
  const decoded = decodeJWT(token);

  if (!decoded) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  res.locals.shopify = { session: { accessToken: decoded.dest_access_token } };
  next();
};

export default verifyToken;

 

 

import the `verifyToken` middleware in your server-side code

 

 

import verifyToken from "./middleware/verifyToken.js";

 

 

Replace this line

 

app.use("/api/*", shopify.validateAuthenticatedSession());

 

 

With this line:

 

app.use("/api/*", verifyToken);

 

 

This middleware should now verify the JWT token sent from the client-side and save the access token in `res.locals.shopify.session.accessToken`.

 

Make sure to restart your server after making these changes.

 

Code Slingin, Pixel Wranglin - Laugh it up at onlinex.com.au
Ninad27
Shopify Partner
6 0 0

from above example, what is this part -

decoded.dest_access_token

 

I am able to decode the token and get data, but I don't have any dest_access_token key in decoded part, I have checked with jwt.io as well, all I have is this -

{
"iss": "xxxx",
"dest": "xxxx",
"aud": "xxxxx",
"sub": "xxxx",
"exp": xxxx,
"nbf": xxxx,
"iat": xxxx,
"jti": "xxxx",
"sid": "xxxx"
}

 

okur90
Shopify Partner
126 20 19

The `dest_access_token` should be replaced with the appropriate field from the decoded JWT token.

 

In your decoded JWT token, you have a `sid` field, which might be the session ID. You can use this session ID to retrieve the access token from your Redis storage.

 

update the `verifyToken` middleware to store the session ID-

 

// middleware/verifyToken.js
import decodeJWT from "../utils/decodeJWT.js";

const verifyToken = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  const token = authHeader.split(" ")[1];
  const decoded = decodeJWT(token);

  if (!decoded) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  res.locals.shopify = { session: { sessionId: decoded.sid } };
  next();
};

export default verifyToken;

 

 

And then, you need to create a middleware to retrieve the access token from Redis storage using the session ID. You can use the `shopify.sessionStorage` instance you created in your `shopifyApp` configuration.

 

 

// middleware/retrieveAccessToken.js
const retrieveAccessToken = async (req, res, next) => {
  const sessionId = res.locals.shopify.session.sessionId;
  const session = await shopify.sessionStorage.loadSession(sessionId);

  if (!session) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  res.locals.shopify.session.accessToken = session.accessToken;
  next();
};

export default retrieveAccessToken;

 

 

Lastly update your server-side code to use both middlewares-

 

import verifyToken from "./middleware/verifyToken.js";
import retrieveAccessToken from "./middleware/retrieveAccessToken.js";

// ...

app.use("/api/*", verifyToken, retrieveAccessToken);

 

 

The access token should be stored in `res.locals.shopify.session.accessToken` after going through both middlewares.

 

Code Slingin, Pixel Wranglin - Laugh it up at onlinex.com.au
Ninad27
Shopify Partner
6 0 0

I am using
 "@shopify/shopify-app-express": "^2.0.0",

so sessionStorage does not exists -
shopify.sessionStorage.loadSession(sessionId)

 

We can fetch data from Redis directly but sessionId does not match with anything. in Redis I can see below data - 

"[[\"id\",\"xxxx\"],[\"shop\",\"xxxx\"],[\"state\",\"xxxx\"],[\"isOnline\",false],[\"accessToken\",\"xxxx\"],[\"scope\",\"xxxxx"]]"

 

jlogey94
Shopify Partner
9 0 1

I started getting "session token is not valid" after I deleted my app then installed it again. Tried a whole host of changes and after hours didn't understand what the issue was. I implemented this and it's fixed it. Still don't understand what the cause was but thank you for this.