Covers all questions related to inventory management, order fulfillment, and shipping.
Hey everyone!
I'm building an embedded app with Next.js as my frontend, and Next.js api routes as my serverless backend.
I have set my backend up to accept and verify JWT session tokens, which seems to work fine. However my problem lies in my way of sending authenticated requests from my frontend to my backend.
I have set up the app bridge and was using getSessionToken(app) function to get the current JWT, but since that is only valid for one minute I have to request a the newest one on every request.
Since I'm using axios for requests to my backend, I followed this tutorial: https://shopify.dev/tutorials/use-session-tokens-with-axios. But after implementing that, I'm getting the following errors on my frontend: TypeError: Cannot read property 'subscribe' of undefined.
This is the exact code for my axios intercept:
import axios from 'axios';
import { getSessionToken } from "@shopify/app-bridge-utils";
const instance = axios.create({
baseURL: process.env.DEV_TUNNEL_URL,
headers: {
post: { // Can be common or any other method
'Content-Type': 'application/json'
},
patch: {
'Content-Type': 'application/json'
},
delete: {
'Content-Type': 'application/json'
},
}
});
// Intercept all requests on this axios instance
instance.interceptors.request.use((config) => {
console.log(window.app)
return getSessionToken(window.app) // requires an App Bridge instance
.then((token) => {
// Append your request headers with an authenticated token
config.headers["Authorization"] = `Bearer ${token}`;
return config;
});
});
I also noticed that window.app is undefined. How can I get the correct app reference in a react app using axios intercept?
I hope you can help me shin some light on this (-:
Thanks very much in advance!
Hey there,
Yeah, I've been playing with this all day today. Got the same error, not defined.
I've ended up creating an app by passing the API_KEY and shopOrigin on _app.js
const app = createApp({
apiKey: API_KEY,
shopOrigin: shopOrigin
});
I have both of those vars defined in _app.js. Looks like a hack but it's the only way I got it to work.
The docs are a bit confusing to me.
import axios from 'axios';
import { useAppBridge } from '@shopify/app-bridge-react';
import { getSessionToken } from '@shopify/app-bridge-utils';
const app = useAppBridge();
You can use useAppBridge to gain access to app also.
Thank you for your answer! Will this also work outside of a react component?
Hey. No not exactly I'm afraid.
I ended up using the authenticatedFetch function that app bridge provides. But I would prefer to use axios to be honest. Let me know if you figure it out (-:
I could get it to work in that way:
File: _app.js
function MyProvider(props) {
const app = useAppBridge();
// Create axios instance for authenticated request
const authAxios = axios.create();
// intercept all requests on this axios instance
authAxios.interceptors.request.use(function (config) {
return getSessionToken(app)
.then((token) => {
// append your request headers with an authenticated token
config.headers["Authorization"] = `Bearer ${token}`;
return config;
});
});
const Component = props.Component;
return (
<Component {...props} authAxios={authAxios} />
);
}
Im passing authAxios down to Component and use axios in that way, when i need authenticated requests.
authAxios.get('/api/products')
.then(result => console.log(result))
.catch(error => console.log(error))
Hope it helps.
Awesome! I will give that a try, thank you.
Evening Sir!
Any luck on this?
We are facing a very similar issue.
I'm very new to react/node. I tried to use what you provided with the shopify CLI boilerplate app and it doesn't seem to work. Can you tell me what I've done wrong? Thanks!
function MyProvider(props) {
const app = useAppBridge();
// Create axios instance for authenticated request
const authAxios = axios.create();
// intercept all requests on this axios instance
authAxios.interceptors.request.use(function (config) {
return getSessionToken(app)
.then((token) => {
// append your request headers with an authenticated token
config.headers["Authorization"] = `Bearer ${token}`;
return config;
});
});
const client = new ApolloClient({
fetch: authenticatedFetch(app),
fetchOptions: {
credentials: "include",
},
});
const Component = props.Component;
return (
<ApolloProvider client={client}>
<Component {...props} authAxios={authAxios} />
</ApolloProvider>
);
}
i'm getting 'jwt expired' error
we're having a similar issues using axios interceptor. Any update. Thanks.
Hey, thanks for this explanation.
can you please show a fully page code that use the authAxios?
Hi HerculesApps,
I've applied your solution, but I'm getting a 302 status and HTML as a response when I make a API call request front side. Do you have any idea of what is going on ?
Seems like hook can be used in functional component.
the question is to use in app instance in the interceptor for the Axios instance. there is no component here and you can not use probably
const app = useAppBridge()
Here is how solve it. Since I can use hook here I did use "app = useAppBridge()" and add JWT token to header and pass header to Axios instance. useFetchDataApiShopify is kind of single point to make api call. I was trying to use interceptor to add token to header but I was not able to use useAppBridge in the interceptor.
mport React from 'react';
import AxiosShopify from '../../AxiosShopify';
import { useAppBridge } from '@shopify/app-bridge-react';
import { getSessionToken } from '@shopify/app-bridge-utils';
const config = {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
};
/*
this is custom hook that fetch data from my backend
*/
export const useFetchDataApiShopify = (url) => {
const app = useAppBridge();
const [responseData, setResponseData] = React.useState(undefined);
const [isError, setIsError] = React.useState(undefined);
const [isLoading, setIsLoading] = React.useState(undefined);
React.useEffect(() => {
const fetchData = async (url) => {
setIsLoading(true);
try {
const token = await getSessionToken(app);
config.headers['Authorization'] = `Bearer ${token}`;
const result = await AxiosShopify.get(url, config);
setResponseData(await result.data);
console.log('set response fetch11', result);
setIsLoading(false);
} catch (error) {
// console.log('set response fetch error', error);
setIsError(error);
}
};
fetchData(url);
}, [url]);
return [responseData, isError, isLoading];
};
//*****Interceptor file became like this *******
mport React from 'react';
import axios from 'axios';
import { getSessionToken } from '@shopify/app-bridge-utils';
const AxiosShopify = axios.create();
AxiosShopify.interceptors.request.use(function (config) {
// return getSessionToken(window.app) // requires a Shopify App Bridge instance
// .then((token) => {
// // Append your request headers with an authenticated token
// config.headers['Authorization'] = `Bearer ${token}`;
// return config;
// });
return config;
});
// Export your Axios instance to use within your app
export default AxiosShopify;
I'm using Axios this way:
const axios = userLoggedInAxios(app);
axios('url', {/* your options */}
This has identical behavior as userLoggedInFetch, showcased in shopify-app-template-node
Here the required module:
/**
* Adds Shopifys Authorization Header to Axios requests through the use of a request interceptor
* app Shopify app instance
* @returns AxiosInstance
*/
function authenticatedAxios(app) {
const axiosShopify = axios.create();
axiosShopify.interceptors.request.use((config) =>
getSessionToken(app) // requires a Shopify App Bridge instance
.then((token) => {
// Append your request headers with an authenticated token
config.headers["Authorization"] = `Bearer ${token}`;
return config;
})
);
return axiosShopify;
}
/**
* Assures that Shopify Request is made for a logged-in user through the use of a response interceptor
* It uses Shopifys redirect mechanism to redirect unauthenticated user-requests to the '/auth' endpoint
* app Shopify app instance
* @returns AxiosInstance
*/
export function userLoggedInAxios(app) {
const authAxios = authenticatedAxios(app);
authAxios.interceptors.response.use((response) => {
if (
response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
) {
const authUrlHeader = response.headers.get(
"X-Shopify-API-Request-Failure-Reauthorize-Url"
);
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.APP, authUrlHeader || "/auth");
return null;
}
return response;
});
return authAxios;
}