Open emilaleksanteri opened 11 months ago
I've just come across the same problem, @emilaleksanteri did you manage to find a workaround?
@numman-ali just didn't use instagram oauth, it looks like meta also doesn't want you to use instagram as an oauth method as seen in the docs limitations: https://developers.facebook.com/docs/instagram-basic-display-api/
@emilaleksanteri I found a solution that will allow the instagram authentication to work with next auth. You need to factor in two points:
To compensate for all of the above, I debugged along the authorization flow and found where the patching for the request was needed. When oauth4webapi makes a POST
request to https://api.instagram.com/oauth/access_token
, the response requires patching of the body to contain token_type
as "bearer"
and then swapping out the short access token for a long lived access token.
Anyway, to make this easier for anyone else, all you need to do is use this fetch intercepter I created within the NextAuth GET router handler. Code for both is below. Let me know if you have any issues.
Instagram Provider set up to pass in additional scopes
export const InstagramAuthProvider: Provider = Instagram({
clientId: process.env.AUTH_INSTAGRAM_ID,
clientSecret: process.env.AUTH_INSTAGRAM_SECRET,
authorization:
"https://api.instagram.com/oauth/authorize?scope=user_profile,user_media",
/**
* Profile is not set for instagram as it cannot be used to authenticate a user,
* only for fetching media and user info.
*/
});
Custom Instagram Fetch Interceptor
// instagram-fetch.interceptor.ts
/**
* This interceptor is used to modify the response of the instagram access token request as it does not strictly follow the OAuth2 spec
* - The token_type is missing in the response
* @param originalFetch
*/
export const instagramFetchInterceptor =
(originalFetch: typeof fetch) =>
async (
url: Parameters<typeof fetch>[0],
options: Parameters<typeof fetch>[1] = {},
) => {
/* Only intercept instagram access token request */
if (
url === "https://api.instagram.com/oauth/access_token" &&
options.method === "POST"
) {
const response = await originalFetch(url, options);
/* Clone the response to be able to modify it */
const clonedResponse = response.clone();
const body = await clonedResponse.json();
/* Get the long-lived access token */
const longLivedAccessTokenResponse = await originalFetch(
`https://graph.instagram.com/access_token?grant_type=ig_exchange_token&client_secret=${process.env.AUTH_INSTAGRAM_SECRET}&access_token=${body.access_token}`,
);
const longLivedAccessTokenResponseBody =
await longLivedAccessTokenResponse.json();
body.access_token = longLivedAccessTokenResponseBody.access_token;
body.token_type = "bearer";
body.expires_in = longLivedAccessTokenResponseBody.expires_in;
// Calculate the `expires_at` Unix timestamp by adding `expires_in` to the current timestamp
const currentTimestampInSeconds = Math.floor(Date.now() / 1000); // Current Unix timestamp in seconds
body.expires_at =
currentTimestampInSeconds + longLivedAccessTokenResponseBody.expires_in;
body.scope = "user_profile user_media";
/* Create a new response with the modified body */
const modifiedResponse = new Response(JSON.stringify(body), {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
/* Add the original url to the response */
return Object.defineProperty(modifiedResponse, "url", {
value: response.url,
});
}
return originalFetch(url, options);
};
Next auth advanced configuration for interceptor and disabling login/signup via instagram:
// app/api/auth/[...nextauth]/route.ts
import { type NextRequest, NextResponse } from "next/server";
import {
auth,
GET as AuthGET,
instagramFetchInterceptor,
POST as AuthPOST,
} from "@/auth";
const originalFetch = fetch;
export async function POST(req: NextRequest) {
return await AuthPOST(req);
}
export async function GET(req: NextRequest) {
const url = new URL(req.url);
if (url.pathname === "/api/auth/callback/instagram") {
const session = await auth();
if (!session?.user) {
/* Prevent user creation for instagram access token */
const signInUrl = new URL("/?modal=sign-in", req.url);
return NextResponse.redirect(signInUrl);
}
/* Intercept the fetch request to patch access_token request to be oauth compliant */
global.fetch = instagramFetchInterceptor(originalFetch);
const response = await AuthGET(req);
global.fetch = originalFetch;
return response;
}
return await AuthGET(req);
}
Disclaimer: Meta do not allow authorization using instagram, to verify a user an individual you must use facebook auth. To mitigate this and allow for passing instagram review process, I have hard disabled login to our app via instagram by rejecting the instagram connection if it being attempted by an unauthenticated user (ie a user that has logged in via another provider).
Hey @numman-ali, thanks for your contribution! What does this line const session = await auth()
do? It seems like you haven't attached the code for it. I noticed you import it from @/auth
.
same issue on the foursquare provider
Provider type
Instagram
Environment
System: OS: Linux 6.5 EndeavourOS CPU: (16) x64 AMD Ryzen 7 4800HS with Radeon Graphics Memory: 3.54 GB / 15.05 GB Container: Yes Shell: 5.1.16 - /bin/bash Binaries: Node: 16.20.2 - ~/.local/share/pnpm/node npm: 8.19.4 - ~/.local/share/pnpm/npm pnpm: 8.8.0 - /usr/bin/pnpm Browsers: Chromium: 117.0.5938.149
Reproduction URL
https://github.com/emilaleksanteri/igauthtest
Describe the issue
When using signin with an instagram provider, I get an error:
[auth][cause]:OperationProcessingError: "response" body "token_type" property must be a non-empty string at processGenericAccessTokenResponse (file:///home/emil/work/storefront/node_modules/.pnpm/oauth4webapi@2.3.0/node_modules/oauth4webapi/build/index.js:895:15)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Module.processAuthorizationCodeOAuth2Response (file:///home/emil/work/storefront/node_modules/.pnpm``/oauth4webapi@2.3.0/node_modules/oauth4webapi/build/index.js:1054:20)
at async handleOAuth (file:///home/emil/work/storefront/node_modules/.pnpm/@auth+core@0.16.1/node_modules/@auth/core/lib/oauth/callback.js:84:18)
at async Module.callback (file:///home/emil/work/storefront/node_modules/.pnpm/@auth+core@0.16.1/node_modules/@auth/core/lib/routes/callback.js:20:41)
at async AuthInternal (file:///home/emil/work/storefront/node_modules/.pnpm/@auth+core@0.16.1/node_modules/@auth/core/lib/index.js:65:38)
at async Proxy.Auth (file:///home/emil/work/storefront/node_modules/.pnpm/@auth+core@0.16.1/node_modules/@auth/core/index.js:100:30)
at async isAuthCall (/home/emil/work/storefront/src/hooks.server.ts:123:12)
at async JWTCheck (/home/emil/work/storefront/src/hooks.server.ts:116:10)
at async Module.respond (/home/emil/work/storefront/node_modules/.pnpm``/@sveltejs+kit@1.25.2_svelte@4.2.1_vite@4.4.11/node_modules/@sveltejs/kit/src/runtime/server/respond.js:282:20) [auth][details]:
{ "provider": "instagram"}
How to reproduce
npm run dev, with valid instagram auth creds hooked up to the --host vite uri and try sign in with instagram
Expected behavior
instagram sign in to work