I am developing a Shopify (Remix) app that uses Instagram Basic API to fetch Reels from user's account. The user is prompted to log in by Instagram to get the access token. After the user logs in, Instagram redirects the user to a URL provided by me (redirect_url).
However the problem is, my app is embedded so let's say the user is using my app on https://shopname.myshopify.com/app/appname, Instagram redirects the user to my app's domain (which I have provided), which kind of works but it immediately takes the user to /login rote (since the app is not authenticated in the new window)
I created this route in my remix app instagram.$.tsx to handle the flow:
import type { LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { CLIENT_ID } from "~/utils.ts/instagram/creds.common";
import { CLIENT_SECRET } from "~/utils.ts/instagram/creds.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const url = new URL(request.url);
const code = url.searchParams.get("code");
// If code is present, then we need to exchange it for an access token
if (code) {
const response = await fetch(
"https://api.instagram.com/oauth/access_token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: "authorization_code",
redirect_uri: `${process.env.SHOPIFY_APP_URL}/instagram`,
}),
},
);
const data = await response.json();
const accessToken = data?.access_token;
if (accessToken) {
const response = await fetch(
`https://graph.instagram.com/me/media?fields=id,username,media_type,media_url,thumbnail_url&access_token=${accessToken}`,
);
const data = await response.json();
console.log("Media Data:", data);
return data?.data;
}
}
return [];
};
type InstagramMedia = {
id: string;
media_url: string;
media_type: "IMAGE" | "VIDEO";
};
const renderMedia = (media: InstagramMedia) => {
switch (media.media_type) {
case "IMAGE":
return (
<img
key={media.id}
src={media.media_url}
alt="Instagram Media"
style={{ width: "100%" }}
/>
);
case "VIDEO":
return (
<video muted key={media.id} style={{ width: "100%" }}>
<source src={media.media_url} type="video/mp4" />
</video>
);
}
};
export default function Instagram() {
const data = useLoaderData<typeof loader>();
return (
<div>
<h1>Instagram Catalogue</h1>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
{data.map(renderMedia)}
</div>
</div>
);
}
This is how the user is redirected to Instagram authentication:
const handleInstagramImportClick = () => {
window.open(
`https://api.instagram.com/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${redirectUrl}&scope=${SCOPE}&response_type=code`,
"_blank",
);
};
Ideally I would like the entire flow to be completed in the same window that the user is on. Do you guys have any insights?
If you're trying to handle authentication in a popup window and pass data back to your main application, using the postMessage API is an effective approach. Here's how you can implement it in your usecases:
window.opener.postMessage({ token: encryptedToken }, "https://your-main-app-url.com");
Listen for the Message in the Main Window: In your main application, add an event listener to receive the message from the popup window. Once you receive the token, you can decrypt it (if necessary) and use it as needed.
// Example: Receiving the message in the main window window.addEventListener("message", (event) => { if (event.origin === "https://your-popup-url.com") { const decryptedToken = decrypt(event.data.token); // Use the decrypted token as needed } });