Search code examples
instagraminstagram-apishopify-appremix.run

How can I make Instagram Basic API authentication work in a Shopify embedded app (Remix Template)?


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?


Solution

  • 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:

    1. Open a Popup Window for Authentication: First, open a popup window where the authentication process takes place. For example, with Instagram, you direct users to the Instagram login page in the popup.
    2. Handle the Redirect and Get the Access Token: After the user authenticates, Instagram will redirect to a specified URL with an access token.
    3. Exchange for a Long-Lived Token (if required).
    4. Use postMessage to Send the Token Back: In the popup window, use the postMessage API to send the token back to your main application window. This keeps your main app separate from the authentication flow (Ensure that's it encrypted though for the safety reasons).

    window.opener.postMessage({ token: encryptedToken }, "https://your-main-app-url.com");

    1. 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 } });