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!
Advertisement
Answer
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
- Go to your “instagram-token” secret and “edit secret”.
- Rotation -> custom -> every 50 days
- Notifications -> Add Topic -> Select “instagram-token-refresh”
- Save
Now every 50 days your “instagram-token-refresh” pub/sub will be triggered.
Create Cloud Function
- Search cloud functions -> enable -> create cloud function
- Function name: “Refresh-Instagram-Token”
- Trigger: pub/sub -> Select “instagram-token-refresh”
- click next
- Entry Point: “refreshInstaToken”
- Edit files:
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
- Go to your secret -> permission
- Add -> {project-id}@appspot.gserviceaccount.com
- Add role “Secret Manager Admin”
Accessing Secret Manager from service account
- Create new service account name “instagram-token”.
- In new service account -> keys -> add keys -> save to desktop
- Go to your secret -> permission -> add -> “instagram-token…gserviceaccount.com” and give the role of “Secret Manager Secret Accessor”
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
- paste to variable so you will have something like
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 🙂