Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OAuth support to bolt #478

Closed
4 tasks done
stevengill opened this issue Apr 20, 2020 · 8 comments
Closed
4 tasks done

Add OAuth support to bolt #478

stevengill opened this issue Apr 20, 2020 · 8 comments
Assignees
Labels
enhancement M-T: A feature request for new functionality
Milestone

Comments

@stevengill
Copy link
Member

Description

Let's add OAuth support to bolt!

It can use the new OAuth package we've been developing at slackapi/node-slack-sdk#963.

The idea being, when you are initializing your app, by passing in a few new options, bolt would automatically setup a route for direct install and a route for oauth redirect. It would also take care of creating a state param for you and exchanging that + the code for an access token. Lastly, it will provide an interface to plug your own database solution to store and retrieve access tokens and other installation related info.

const app = new App({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET,
  stateSecret: 'my-state-secret',
  scopes: ['channels:read', 'groups:read', 'channels:manage', 'chat:write'],
  metadata: 'some_metadata',
});

What type of issue is this? (place an x in one of the [ ])

  • enhancement (feature request)

Requirements (place an x in each of the [ ])

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue.
@stevengill stevengill self-assigned this Apr 20, 2020
@stevengill stevengill added the enhancement M-T: A feature request for new functionality label Apr 20, 2020
@stevengill stevengill added this to the v2.1 milestone Apr 20, 2020
@junjizhi
Copy link

The code example in the PR(#479) is not complete:

const app = new App({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET
  stateSecret: 'my-state-secret',
  scopes: ['channels:read', 'groups:read', 'channels:manage', 'chat:write', 'incoming-webhook'],
  installationStore: {
    storeInstallation: (installation) => {
      // change the line below so it saves to your database
      return database.set(installation.team.id, installation);
    },
    fetchInstallation: (InstallQuery) => {
      // change the line below so it fetches from your database
      return database.get(InstallQuery.teamId);
    },
  },
});

It is missing the authorize function. So we will see the error:

Error: No token and no authorize options provided. Apps used in one workspace should be initialized with a token. Apps used in many workspaces should be initialized with a authorize.

What do you think about adding the needed param for the code example? Something like:

const authorizeFn = async (installation) => {
  return {
    botToken: process.env.SLACK_BOT_TOKEN,
    botId: "xxx",
    botUserId: "xxx",
  };
};
const app = new App({
  authorize: authorizeFn,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  //...
});

@stevengill
Copy link
Member Author

hey @junjizhi,

When using the built in OAuth, you don't provide authorize. In fact, bolt will error out if you provide authorize as well as the options the built in OAuth needs. The built in OAuth has its own authorize. If you are seeing that error, I would imagine that you haven't upgraded to the latest bolt version.

Let me know if that fixes it for you!

@junjizhi
Copy link

@stevengill You are right. The default @slack/bolt version Glitch uses is 1.6.1. After upgrading to 2.1.1 the error went away. Thanks!

@abdel-ships-it
Copy link

Would this work locally in combination with ngrok? I followed the example in the docs but can't get it to work using any URL combination. The worst part is that I'm not getting any errors thrown beside 404's.

@stevengill
Copy link
Member Author

@realappie it should work fine with ngrok.

In your App config page on api.slack.com, navigate to OAuth & Permission page and add a redirect_url. If using ngrok and the default values of the OAuth package, it would be something like https://c16018c5.ngrok.io/slack/oauth_redirect.

Once your app is running, go to https://c16018c5.ngrok.io/slack/install (use your local ngrok url of course) and you should see an add to slack button that will kick off the OAuth flow.

@abdel-ships-it
Copy link

@stevengill Do you have a fully working example of this anywhere? I pretty much wrote everything in the documentation regarding oauth here. Without any success :/ I have also tried the install link without any success. I also currently have a working setup with a custom express route, but would love to switch over to the built-in implementation.

My custom implementation This is perhaps useful to share for anyone that can't get the built-in implementation to work either.
export const createSlackAuthServer = (slackExpressReceiver: ExpressReceiver, slackApp: App, firestore: FirebaseFirestore.Firestore) => {

 const app_express = slackExpressReceiver.app;

 /* oauth callback function */
 app_express.get('/auth', async (req, res, next) => {

   const code: string = req.query['code'] as string;

   if (isNil(code)) {
     res.status(500).send('No code query param found');
   }

   try {
     const result = await slackApp.client.oauth.v2.access({
       client_id: config.slack.client_id,
       client_secret: config.slack.client_secret,
       code: code
     });

     const teamId: string = get(result, 'team.id') as string;

     const accessToken = get(result, 'access_token') as string;

     const authTestResponse: unknown = await slackApp.client.auth.test({
       token: accessToken
     });

     const authTestResponseTyped = authTestResponse as SlackAuthTestResponse;

     const installationDocument: SlackInstallationDocument = {
       botId: authTestResponseTyped.bot_id,
       teamId,
       botUserId: get(result, 'bot_user_id') as string,
       botToken: accessToken,
       user_id: authTestResponseTyped.user_id,
     };

     const installingUserId = get(result, 'authed_user.id') as string;

     const enterpriseId = get(result, 'enterprise.id') as string;

     if (enterpriseId) {
       installationDocument.enterprise_id = enterpriseId;
     }

     await firestore.collection('installations').doc(teamId).set(installationDocument);

     // Open up the conversation in slack upon success
     //
     res.redirect(`https://slack.com/app_redirect?app=${config.slack.app_id}`)

     await slackApp.client.chat.postMessage({
       token: accessToken,
       channel: installingUserId,
       text: '',
       blocks: [{
           "type": "section",
           "text": {
             "type": "mrkdwn",
             "text": `Hi there! Welcome to the bitrise bot. To start using this bot please set a bitrise access token first.`
           }
         },
         slackBlockCreator.generateSetBitriseTokenModalActionBlock()
       ]
     });

     console.error(`[slack-auth] installation successful for teamId="${teamId}" enterpriseId="${enterpriseId}"`);
   } catch (error) {
     console.error('[slack-auth] error calling client.oauth.v2.access', error);

     res.status(500).send('Error authenticating');
   }
 });
}

And wherever you're creating a new slack App do the following

const slackExpressReceiver = new ExpressReceiver({
  signingSecret: config.slack.signing_secret,
  endpoints: '/events'
});


const slackApp = new App({
  receiver: slackExpressReceiver,
  authorize: async ({ teamId }) => {
    const documentSnapshot = await firestore.collection('installations').doc(teamId).get();

    if ( !documentSnapshot.exists ) {
      throw new Error(`no matching installations for team="${teamId}"`);
    } else {
      console.log(`[slackApp:authorize] matching installations found for teamId="${teamId}"`)
    }

    const installation = documentSnapshot.data() as SlackInstallationDocument;

    return installation;
  }
});

createSlackAuthServer(slackExpressReceiver, slackApp, firestore); 

@seratch
Copy link
Member

seratch commented Aug 18, 2020

@realappie

By providing clientId, clientSecret, stateSecret and scopes when initializing App, Bolt for JavaScript will handle the work of setting up OAuth routes and verifying state.

As mentioned in the Bolt JS document, you need to pass at least clientId, clientSecret, and stateSecret to the App's constructor. Otherwise, the OAuth flow support won't be enabled.

If you have a reason to manually initialize ExpressReceveier as you do in the code you've shared, give those arguments to its constructor.

@stevengill
Copy link
Member Author

@realappie see my answer at #583 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement M-T: A feature request for new functionality
Projects
None yet
Development

No branches or pull requests

4 participants