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

Correct Authorization Flow for Workspace Bot *and then* generate User tokens later #574

Closed
stantonius opened this issue Jan 14, 2022 · 6 comments
Labels
area:sync question Further information is requested

Comments

@stantonius
Copy link

First of all, loving the Slack Bolt dev experience. And really appreciate the community and responsiveness from maintainers.

I have put this as a question because I'm pretty sure I am missing a fundamental Oauth step here - I just can't figure it out.

As described in the Expected Result section below, I am trying to create an App that is both a Bot and also uses the Web API that requires user_tokens. The Bot and Home page are meant to be present regardless of whether the user has a user_token stored or not - the Home Page UI and Bot responses will just depend on whether the user_token exists. While I have the whole setup working, the flow is really choppy. I can create the App for the workspace, however when a user then goes to interact with the App (either via the Home page or the Bot), the Bolt App returns the error slack_bolt.MultiTeamsAuthorization:Although the app should be installed into this workspace, the AuthorizeResult (returned value from authorize) for it was not found.

Note that if the user first goes to the /slack/install route from a browser, the user_tokens are generated and if you allow Slack to be opened, the permissions are recognised and the user can interact with the App's advanced functionality that requires the user_tokens

I know from the Bolt docs that apps that span multiple workspaces may require an authorize function, however I had never selected or implied that this was a multi-workspace app, so I am unsure why the App is expecting an AuthorizeResult returned.

I also know there have been similar questions asked before, however what I am experiencing is that this error is introduced before any of the event callbacks are triggered. Ie. I can't do any of my own validation on the "app_home_opened" event callback as this is never triggered, and thus I don't have access to the client object unless I recreate one somehow. As you can see in the screenshot below, the only object I have access to currently (without the custom authorize function) is the initial Request json. Even the Oauth_flow callbacks are not triggered (where I could also set some custom logic), which suggests this MultiTeamsAuthorization error occurs before the Oauth_flow is validated.

Screenshot 2022-01-14 at 11 43 05 AM

Is my only solution for this to create a custom authorize function to handle all of the permission checks each time? This is not ideal as one of the advantages of using Bolt is that it handles the Oauth protocol and token verification for us, which I would like to avoid writing myself (and possibly introducing security vulnerabilities)

Many thanks for any guidance available

Reproducible in:

The slack_bolt version

slack-bolt==1.11.1
slack-sdk==3.13.0

Note that FastAPI adapter is used

Python runtime version

3.9.9

OS info

ProductName: macOS
ProductVersion: 12.0.1
BuildVersion: 21A559

Steps to reproduce:

See below

Expected result:

  1. App is installed to entire workspace. Bot token is generated and embedded in the Bolt code
  2. User interacts with Bot to understand its advanced functionality. Bot evaluates whether there is a user_token for each user.
  3. If yes, the advanced App functionality is presented to the user.
  4. If no, the Home Page explains the App, its rationale for permissions, and has the Install button embedded. The App's Bot is available to address general questions about it and its features. Once the Install is complete, the Home page then loads with the advanced features, since the user_token has been generated

Actual result:

  1. App is installed to entire workspace. Bot token is generated and embedded in the Bolt code
  2. User clicks on the App (to see either the Home page or interact with the Bot), and the Bolt Python App returns the error slack_bolt.MultiTeamsAuthorization:Although the app should be installed into this workspace, the AuthorizeResult (returned value from authorize) for it was not found. The Home page never loads and the Bot is not functional.
@seratch seratch added area:sync question Further information is requested labels Jan 14, 2022
@seratch
Copy link
Member

seratch commented Jan 14, 2022

Hi @stantonius, thanks for asking the question! Also, we're glad to hear that you like bolt-python!

App is installed to entire workspace. Bot token is generated and embedded in the Bolt code

As your app implements the OAuth for app installations, your installation data is managed in the InstallationStore (in your example code, it's local file system based FileInstallationStore). Thus, you don't need to pass the token argument to your App initialization. Bolt automatically resolves relevant bot tokens for each incoming request from Slack.

Also, if you use the installation from the Slack app admin page, the generated token does not work in this way. As your app offers the OAuth flow, all the installations must be done from the /slack/install endpoint.

user scopes

You are passing "users:read", "users:read.email" as both bot scopes and user scopes to the OAuth authorize URL. However, if you need only these scopes, having them in bot scopes would be enough. Also, for app_home_opened events and views.publish API call, you don't need any user scopes.

I hope this helps!

@stantonius
Copy link
Author

Hey @seratch thanks for getting back to me so quickly.

Thus, you don't need to pass the token argument to your App initialization.

Makes sense.

However, if you need only these scopes, having them in bot scopes would be enough. Also, for app_home_opened events and views.publish API call, you don't need any user scopes.

Agreed. Sorry I should have been more explicit - the code I shared was a sample and I had removed some of the Scopes for readability. But this validates my understanding.

generated token does not work in this way. As your app offers the OAuth flow, all the installations must be done from the /slack/install endpoint.

OK so this is where my issue is then. Are you aware of any way to have this integrated approach, where the Bot is available to the workspace and offers this Install button as part of its chat or on its Home page? Otherwise I would need to either:

  • Have 2 Apps (one for this initial UI that is immediately available to all workspace users, the second appears when each user installs via the /slack/install endpoint). Not ideal as these are really the same Apps
  • Distribute the URL by some other mechanism. Also not ideal as I assume this will have lower engagement

Thanks again for your help with this

@seratch
Copy link
Member

seratch commented Jan 14, 2022

@stantonius
I may not fully understand what you want to do here but if you mean you need two OAuth installation flows for 1) enabling your app in the workspace (=bot installation) and 2) individual user installation with user scopes, I would suggest:

  • Create two App instances with the same set of client_id/client_secret
    • 1: App with the oauth_settings for bot installation (with bot scopes)
    • 2: App with the oauth_settings for user scope installation (with user scopes)
  • Have two sets of installation endpoints
    • 1: /slack/app_install and /slack/app_oauth_redirect
    • 2: /slack/user_install and /slack/user_oauth_redirect and use corresponding App instance for each
  • Share a single InstallationStore among them
  • As long as InstallationStore manages all installations, either of 1: or 2: can serve /slack/events. If you want to have a different App for /slack/events, it's also fine.
  • If you want to receive a user token associated with the incoming request, a token can be available as context.user_token as long as the user already installed the app via 2) OAuth flow see also authorize using InstallationStore under the hood

If you are fine to have two Slack apps, of course, things can be much simpler. You can run those apps either in different processes or in different sets of FastAPI endpoints. Either way, having two App instances would be the same.

Does this make sense to you?

@seratch
Copy link
Member

seratch commented Jan 28, 2022

@stantonius Let us know if you have more to ask/discuss here 👋 We'll close this issue after waiting for your response for a few more days.

@stantonius
Copy link
Author

Apologies for the delay and thanks for following up - this team is on it!

The sharing of client ids and installations was an awesome idea. I think I have this working as you mentioned, however I'm still working through a few details when I have time.

For what it is worth, what really tripped me up (which you rightfully mentioned in your response) was that the tokens generated in the App admin portal cannot be referenced in your Bolt code if you are also using Oauth unless you write your own authorization flow for each request - which defeats some of the purpose of using Bolt. I hadn't understood that tokens generated in the admin page do not update in your app's installation yet tokens generated via Oauth do flow back to Slack and are reflected in the admin page. Basically if using Oauth and installations in your app, do not generate the tokens in the admin page - go to the /slack/install endpoint and install this way, and the tokens will still appear in the admin page but also in the installation.

It was the above misunderstanding that caused this MultiTeamsAuthorization/AuthorizeResult error - even when I provided legit tokens generated from the admin portal directly to the App instance, it was ignored and only looked via the default authorize function.

Finally, I did notice some of the feedback to your PR was that this was a complicated workflow/scenario. I had made some additional notes on what I was trying to do in case it was helpful as to why all this was necessary.


The app I am building requires a few scopes be accessible by user_token for additional functionality - I am trying to create a single app that fits the following criteria:

  1. The App is installed at the workspace level and is immediately available to everyone in the workspace
  2. Each individual user is then presented with the option to enable the features that require more access than a workspace bot is allowed.

The rationale for this workflow is to me a good mix of maximizing App engagement (already pre-installed and visible, can nudge and provide more context) while being transparent on permissions and giving the end user full control (progressive permission flow started by the organization, left with the users to ultimately decide on any privileged access) - all within a single app package.


Thanks for all your hard work

@seratch
Copy link
Member

seratch commented Jan 28, 2022

Thanks for your reply!

the token generated via admin page installation vs the OAuth flow

Yes, we have been observing that this difference is one of the confusing points for new Slack app developers. Glad to know you've figured it out but, in the future, our team may be able to improve the documents to help new developers more easily understand this.

the scenario you mentioned in the comment

The pull request #576 should resolve one blocker for your use case. Please try the latest version out. If you have anything further to ask/clarify in your app development, please feel free to open a new question issue.

It seems that there is nothing else we can offer here. Let us close this issue now. Thanks again for your time to write in here!

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

No branches or pull requests

2 participants