Slack app authorization

Hi all,

I’m building an app for Slack using the Bolt framework, and I’m having trouble implementing the app initialization with the “authorize” option. This allows me to distribute the app by storing / initializing multiple tokens / apps, rather than just having one token as an environment variable. Implementing in the way done in Slack’s documentation does not work in my node.js app on glitch, as the “const authorizeFn” is declared after it is required in the app initialization: https://slack.dev/bolt/concepts#authorization

When I try flipping the app & authorizeFn code, it does not work because the app tries to initialize while the authorizeFn function is still running. It ends up succeeding & returning a stored token, but after the app needs it. Here is that version simplified:

const authorizeFn = async ({ teamId }) => {
// Fetch team info from database
// Do stuff to get the token
return {
botToken: data.Items[0].access_token
};
throw new Error(‘No matching authorizations’);
};

const app = new App({
authorize: authorizeFn,
signingSecret: process.env.SLACK_SIGNING_SECRET
});

I’ve been flipping this thing all around and searching all over to figure it out, but am still unsure how to solve this puzzle. I’m sure it’s something around my lack of knowledge around async functions & node.js, but any help is much appreciated - thanks!

Hi Mezoe!

I think what is happening is regarding async functions. Take a look at this code, which is vaguely similar to your code:

async function authorizeFn ({ teamId }) {
  // Fetch team info from database
  docClient.query({/* stuff */}, (err, data) => {
    return {
      botToken: process.env.SLACK_BOT_TOKEN,
      userToken: process.env.SLACK_USER_TOKEN
     });
};

The authorizeFn, when it is called, will start the query going, and then immediately return. It won’t block - it doesn’t know that the callback is important and necessary for the return value from authorizeFn. After the authorizeFn returns, the query will invoke the callback function but at that time it is too late to affect the return value from authorizeFn.

I think you want to do something where you return a Promise immediately from authorizeFn, and use the resolve function to communicate to the Promise what the eventual answer is from the database.

async function authorizeFn ({ teamId }) {
  return new Promise((resolve, reject) => {
    // Fetch team info from database
    docClient.query({/* stuff */}, () => {
      resolve({
        botToken: process.env.SLACK_BOT_TOKEN,
        userToken: process.env.SLACK_USER_TOKEN
      });
    });
  });
};

Hope this helps,

Johnicholas

Thanks for the help Johnicholas - and for the quick response!

I understand the concept, but think that I am implementing it incorrectly, as I’m getting the same delay issue. Here is what I have currently:

const authorizeFn = async ({ teamId }) => {
  return new Promise((resolve, reject) => {
    // Fetch team info from database
    docClient.query({
      /// Stuff
      }
    }, (err, data)=>{
      if(err) {
        console.log(err);
      } else {
        console.log(data);
        if (data.Count == 1) {
          resolve ({
            botToken: data.Items[0].access_token
          });
        }
      }
    })
    throw new Error('No matching authorizations');
  });
};

const app = new App({
  authorize: authorizeFn,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

I also tried having the initialization of the app done similarly to your example…

const app = async ({ teamId }) => {
  return new Promise((resolve, reject) => {
    // Fetch team info from database
    docClient.query({
      // Stuff
      }
    }, (err, data)=>{
      if(err) {
        console.log(err);
      } else {
        console.log(data);
        if (data.Count == 1) {
          resolve (new App ({
            token: data.Items[0].access_token,
            signingSecret: process.env.SLACK_SIGNING_SECRET
          }));
        }
      }
    })
    throw new Error('No matching authorizations');
  });
};

This however doesn’t seem to work, as I get an error on the following functions. I think it’s just because it’s not properly initializing const app

Bumping this, I’m still stumped!

In chatting with Slack dev support, they suggested that there might be a way to authenticate the user in advance, and then initialize the app with that once an event comes in. However I’m not sure how you would store multiple tokens locally in their own app instance like that, which is why I seem stuck just retrieving it from my database…

Have you tried

const app = new App({
  authorize: await authorizeFn,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

Hi @edwrddd - thank you for the suggestion!

I have tried that; the problem there is that it has to be in an async function for await to be used. Then if I put the app initialization in an async function, the variable is no longer global… :slightly_frowning_face:

Not sure if you saw this, but Slack Bolt v2.1.1 now has OAuth built in!
https://slack.dev/bolt-js/concepts#authenticating-oauth

Very easy to use and get set up provided you’ve got a database to store the tokens in (I’ve been using nedb which works in the local filesystem on Glitch)