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

Bolt Slack API is very unclear on multiple workspace install #1076

Open
richarddli opened this issue May 7, 2024 · 5 comments
Open

Bolt Slack API is very unclear on multiple workspace install #1076

richarddli opened this issue May 7, 2024 · 5 comments
Assignees
Labels
question Further information is requested

Comments

@richarddli
Copy link

I'm creating a multiple workspace Slack bot using Bolt Python.

The documentation is very clear about the OAuth flow: https://slack.dev/bolt-python/concepts#authenticating-oauth. everything points to this. However, the documentation says nothing about what happens after you get the bot access token.

With a great amount of research, I found that the BoltJS (not Python) has more context. https://slack.dev/bolt-js/concepts#authenticating-oauth. In particular, it references an InstallationStore. So, I Google some more and end up finding the Slack SDK page for Token Lookup: https://slack.dev/python-slack-sdk/oauth/index.html#token-lookup.

Based on the design pattern on this page, it seems to suggest that I instantiate a new WebClient for each request with its own token. It's not clear to me if this is the appropriate design pattern for Bolt apps, either.

The page URLs

I'd love to see some sort of example (can't find it) and a link to how you build a multiple workspace app somewhere here: https://slack.dev/bolt-python/concepts#authenticating-oauth. At a minimum, parity with the JS docs would be helpful. But honestly I'm still confused about the appropriate design pattern and if I'm supposed to create a new AsyncApp with every request with its own access token, or if there's a better way.

@hello-ashleyintech hello-ashleyintech self-assigned this May 7, 2024
@hello-ashleyintech hello-ashleyintech added the question Further information is requested label May 7, 2024
@hello-ashleyintech
Copy link

Hi, @richarddli! Thank you for submitting this issue 😄

Bolt Python has built-in OAuth capabilities, as mentioned in the doc that you linked. Essentially, to enable OAuth on the app side, you'll need to initialize your app similar to this, taken from here:

app = App(
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
    installation_store=FileInstallationStore(),
    oauth_settings=OAuthSettings(
        client_id=os.environ.get("SLACK_CLIENT_ID"),
        client_secret=os.environ.get("SLACK_CLIENT_SECRET"),
        scopes=["app_mentions:read", "channels:history", "im:history", "chat:write"],
        user_scopes=[],
        redirect_uri=None,
        install_path="/slack/install",
        redirect_uri_path="/slack/oauth_redirect",
        state_store=FileOAuthStateStore(expiration_seconds=600),
        callback_options=CallbackOptions(success=success, failure=failure),
    ),
)

As you mentioned, for Bolt Python OAuth, you do need some sort of data store solution to store all the credentials for each download of the app to a new workspace. Then, you need to point your app config to use the data store so it knows where to put the credentials for each new download.

In the above example, the code is using a local file as a store with one of our built-in InstallationStore options, but we also have [examples] (https://github.com/slackapi/bolt-python/tree/main/examples) in this repo for another type of built-in store we offer, sqlite3. I would recommend exploring one of our built-in InstallationStore options for you to be able to store the data you need. These docs provide more insight into built-in installation stores we offer.

The above is all you need to get started with OAuth - Bolt handles the rest. You can test installing to multiple workspaces once it's set up using the /slack/install path.

I hope this helps, let me know if you have additional questions! 🙌

@richarddli
Copy link
Author

richarddli commented May 7, 2024

Hi @hello-ashleyintech! Thanks for the prompt response. Yes, I figured all the above out. The other thing that isn't mentioned is how to write your Slack event handler to handle multiple workspaces. This functionality does not seem to be supported directly by Bolt. The Token Lookup page has some hints. Here's where I ended up:

@app.message('hello')
async def message_hello(client, context):
    org_id = context['team_id']
    bot_token = get_access_token(org_id)
    await client.chat_postMessage(
       token=bot_token,
       channel=context['channel_id'],
       text=f'Hey there!'
    )

Is this how it's done? Or is there a better way? Example code is fine. I just couldn't find how to do this in the docs.

@hello-ashleyintech
Copy link

hello-ashleyintech commented May 7, 2024

Hi, @richarddli!

I believe you can just do the following:

await client.chat_postMessage(
       channel=context['channel_id'],
       text=f'Hey there!'
)

Bolt should take a look at the incoming event, verify the installation instance passed into the request via the InstallationStore, and then if all looks good and it has access to the specified channel in the workspace, it'll post it in there.

You can view an example of this here with client.views_open. As you can see, they're not passing in any token, just the other needed params for the API call.

Let me know if you have additional questions!

@richarddli
Copy link
Author

richarddli commented May 13, 2024

Hi @hello-ashleyintech. Thanks for all your help. I've gotten most (?) of this working, but I have one more error I can't figure out.

  1. I've created an OAuth settings that is identical to what you have above, with slightly different scopes:
oauth_settings = AsyncOAuthSettings(
    client_id=os.environ.get("SLACK_CLIENT_ID"),
    client_secret=os.environ.get("SLACK_CLIENT_SECRET"),
    scopes=["app_mentions:read", "channels:history", "im:history", "groups:history", "mpim:history", "chat:write"],
    user_scopes=[],
    redirect_uri=None,
    redirect_uri_path="/slack/oauth_redirect",
    install_path="/slack/install",
    state_store=FileOAuthStateStore(expiration_seconds=300),
    callback_options=CallbackOptions(success=success, failure=failure),
    )
  1. I then register handlers for all of this:
@fastapi_app.post("/slack/events")
async def slack_events(request: Request):
    return await slack_handler.handle(request)

@fastapi_app.get("/slack/install")
async def install(request: Request):
    return await slack_handler.handle(request)

@fastapi_app.get('/slack/oauth_redirect')
async def oauth_redirect(request: Request):
    return await slack_handler.handle(request)
  1. When I go to /slack/install in my browser, I get the install button, which tells me the Slack handler is working. I then initiate the OAuth flow and try to install into my test workspace.

  2. On the redirect to /slack/oauth_redirect, I see an "invalid browser" error message in my browser. My logs show:

INFO:     108.7.77.168:0 - "GET /slack/oauth_redirect?code=6052573369618.7107765928723.4943723e88b5ac78ed48f5e0a98d4346b85af9ec1ffb57e3dbd77f000962b88c&state=fa114bbc-36c4-43c2-acba-516a1a262eea HTTP/1.1" 400 Bad Request

I've verified that my client secret and client ID are correct, and read by the app itself. I also was able to get a successful access_token by taking the code value returned in the URL and sending it in via curl. I'm sure I'm missing something, but a bit mystified as to what?

@hello-ashleyintech
Copy link

Hi, @richarddli! 👋 Sorry to hear you're still running into some issues.

As a starting point for troubleshooting the Invalid browser error, I would recommend taking a look at these two potentially related issues: #179 and #492. Let me know if following the recommendations in these issues end up solving the error!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants