I'm trying to set up an instagram feed (just images and links) of a public instagram account for my Nextjs app.
I know I need to use the Instagram Basic Display API and get a Long-Lived Access Token but it expires after 60 days and I don't want to have to manually refresh it. Does anyone know a good, preferably free, way of doing this automatically?
I have looked at instagram-token-agent but that setup uses Heroku and an add-on that costs $30 a month which seems high.
Any ideas or links would be really helpful, thanks!
I eventually ended up using Google Cloud Secret Manager.
Overview: Secret Manager stores long-lived token and every rotation triggers a pub/sub that then triggers a cloud function. The cloud function refreshes the token for a new one and then adds a new version to the secret.
Name it "instagram-token" and add your long lived token as the secret value. For now leave everything else default and create secret.
Create a service account for secret manager
In your terminal:
gcloud auth login
then
gcloud beta services identity create --service "secretmanager.googleapis.com" --project "YOUR_GCP_PROJECT_ID"
It may ask you to install gcloud beta commands.
IMPORTANT: Make sure you note down the full name of the service account returned in the terminal. If you have lost it, run the same command again.
Create a new topic and name it "instagram-token-refresh", untick 'add a default subscription'.
Give secret manager permission to publish pub/sub
In your new pub/sub topic go to permissions -> Add Principle. Search and add the service account name added above. service-{id}@gcp-sa-secretmanager.iam.gserviceaccount.com. Add the new role Pub/Sub Publisher
Add rotation and pub/sub to secret
Now every 50 days your "instagram-token-refresh" pub/sub will be triggered.
Create Cloud Function
You might need to enable to cloud build API
package.json
{
"name": "refresh-instagram-token",
"version": "0.0.1",
"dependencies": {
"@google-cloud/pubsub": "^0.18.0",
"@google-cloud/secret-manager": "^3.10.1",
"axios": "^0.24.0"
}
}
index.js
// Import the Secret Manager client
const { SecretManagerServiceClient } = require("@google-cloud/secret-manager");
const axios = require('axios');
// name of function is the same as entry point
exports.refreshInstaToken = async (event, context) => {
// check pub/sub message is rotation to prevent infinte looping
const event_type = event && event.attributes.eventType;
//allowing SECRET_VERSION_ENABLE lets you manually trigger this function by disabling the secret and then enabling it (rather than waiting for rotation trigger)
if (event_type != "SECRET_ROTATE" && event_type != "SECRET_VERSION_ENABLE") {
return null;
}
// secret name
const parent = event.attributes.secretId;
const name = parent + "/versions/latest";
// Instantiates a client
const client = new SecretManagerServiceClient();
// get latest secret
const [version] = await client.accessSecretVersion({
name: name,
});
// Extract the payload as a string.
const secret = version.payload.data.toString();
// refresh token
const requesturl = `https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${secret}`;
const response = await axios.get(requesturl);
const data = await response.data;
// data = {"access_token", "token_type", "expires_in"}
// check access_token isn't null
if (data && data.access_token) {
// Payload is the plaintext data to store in the secret
const newSecret = Buffer.from(data.access_token, "utf8");
// add new secret version (the refreshed token)
const [newVersion] = await client.addSecretVersion({
parent: parent,
payload: {
data: newSecret,
},
});
console.log(`Added new secret version ${newVersion.name}`);
// get new secret version number
let newVersionN = newVersion.name.split("/");
newVersionN = newVersionN[newVersionN.length - 1];
if (newVersionN > 1) {
// if is a second version delete one before it
const nameToDestroy = parent + "/versions/" + (newVersionN - 1);
const [deletedVersion] = await client.destroySecretVersion({
name: nameToDestroy,
});
console.info(`Destroyed ${deletedVersion.name}`);
}
}
};
Consume event notifications with Cloud Functions Ref
Give cloud functions permissions to Secret
Accessing Secret Manager from service account
Setup credentials environment variable
create .env.local file in next js root directory
add new empty value GOOGLE_APPLICATION_CREDENTIALS=
Convert JSON file to Base64 key and copy to clipboard MAC
openssl base64 < /Users/{username}/Desktop/service-account.json | tr -d '\n' | pbcopy
Convert JSON file to Base64 WINDOWS
certutil -encode service-account.json encoded.txt
GOOGLE_APPLICATION_CREDENTIALS=faGdfdSytDsdcDg...
Install @google-cloud/secret-manager npm i @google-cloud/secret-manager
const {
SecretManagerServiceClient
} = require("@google-cloud/secret-manager");
export const getInstagramToken = async() => {
// parse your base 64 env variable to a JSON object
const credentials = JSON.parse(
Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS, "base64").toString()
);
// TO DO -> CHANGE
const projectId = "eleanor-daisy";
const secretId = "instagram-token";
// set up credentials config
const config = {
projectId,
credentials,
};
// init secret manager with credentials
const client = new SecretManagerServiceClient(config);
const secretName = `projects/${projectId}/secrets/${secretId}/versions/latest`;
// Access the secret.
const [accessResponse] = await client.accessSecretVersion({
name: secretName,
});
const instaToken = accessResponse.payload.data.toString("utf8");
return instaToken;
};
Add GOOGLE_APPLICATION_CREDENTIALS and key to vercel when deploying.
Done! I might make a video tutorial on this as there's not much out there, let me know if that would be helpful :)