Skip to content

Slack install button provided by slack does not work. Bolt/Slack-SDK appears to reject because of missing state. Is state actually optional? #248

Description

@ipl31

Hello,

I have built a slack app using the python bolt package. It is based off the Lambda examples in bolt-python/examples. I am using the S3/Lambda Oauth class:

oauth_flow = LambdaS3OAuthFlow()
bolt_app = App(process_before_response=True,
               oauth_flow=oauth_flow)

It is working well and I can install the app when I use the install button on my API endpoint. Which you can view here: https://slack.com/oauth/v2/authorize?client_id=1547952482821.1777332262946&scope=app_mentions:read,chat:write,commands&user_scope=

However when I attempt to use the links provided in the slack workspace admin interface, none of them work. For example:
https://slack.com/oauth/v2/authorize?client_id=1547952482821.1777332262946&scope=app_mentions:read,chat:write,commands&user_scope=

This link is provided in the slack app configuration for my app in the slack admin web app. When I attempt to use the buttons provided by slack I get redirected to my App and get an invalid browser error. Specifically "Please try again from here or contact the app owner (reason: invalid_browser)". I am using a supported browser and I am using an TLS endpoint. When I dig into the code I noticed that is_valid_browser() will return False if the state parameter is not set. Specifically this code:

https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/oauth/state_utils/__init__.py#L44

All slack documentation says the state variable is optional but suggested for security. But this seems to indicate that it is mandatory at least with the code I am using. Is it mandatory in the LambdaS3AuthFlow()? If so, then how do I get the slack admin website to render links/buttons that will work?

Reproducible in:

bolt-python lambda oauth example, when using public distribution links from slack website.

The slack_bolt version

slack-bolt==1.4.1
slack-sdk==3.4.0

Python runtime version

(Paste the output of python --version)
Python 3.8.6

OS info

(Paste the output of sw_vers && uname -v on macOS/Linux or ver on Windows OS)
AWS Lambda default runtime.

Steps to reproduce:

Build the bolt-python Lambda Oauth example app. Configure the app in your slack instance. Then setup public distribution and attempt to use the install links/buttons provided by slack.

Expected result:

I expect that when I use the links generated from the slack website (under public distribution) to distribute my app, that they work. However when I use those links I get an invalid browser error. However using the link provided by my api endpoint works fine.

Actual result:

Oops, Something Went Wrong!
Please try again from here or contact the app owner (reason: invalid_browser)

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Activity

  1. seratch commented on Feb 26, 2021

    @seratch
    Contributor

    Hi @ipl31, thanks for asking the question!

    Is it mandatory in the LambdaS3AuthFlow()? If so, then how do I get the slack admin website to render links/buttons that will work?

    Indeed, the state parameter is optional for the Slack OAuth flow but the Bolt framework is a bit opinionated. It requires state parameter handling as long as you use its built-in OAuth module.

    As described in this document, the usage is simple. Once your App spins up with a valid OAuthSettings argument, your app serves /slack/install path and if your end users start the app installation from this URL, your app automatically generates and sets the state value for you.

  2. ipl31 commented on Feb 26, 2021

    @ipl31
    Author

    @seratch Thanks. Yes I just verified that my install URL is setting the state cookie properly but the slack URL does not. Thanks for confirming this. I prefer to use the most secure option. My concern is that the slack buttons provided in the slack admin interface do not work. Once the app is approved and in the app directory will the install links from the app directory set the state properly? Or do I need to always use the install page on my app? Thanks!

  3. ipl31 commented on Feb 26, 2021

    @ipl31
    Author

    @seratch I think I answered my own question after reading through how OAuth 2 works. It looks like the slack app directory must redirect to the app generated install page, since the state value needs to be stored on the app side. So it seems that the buttons/links provided by the slack admin interface will not work if you are using this feature, but the app directory will link to apps install page so its not an issue. Thanks for the response. You can close this request.

  4. seratch commented on Feb 26, 2021

    @seratch
    Contributor

    @ipl31 Thanks for confirming. Let me share a bit more about App Directory app links!

    It looks like the slack app directory must redirect to the app generated install page, since the state value needs to be stored on the app side.
    Once the app is approved and in the app directory will the install links from the app directory set the state properly? Or do I need to always use the install page on my app?

    This is a great question. For App Directory apps, you can customize the installation URL. Set the URL in Basic Information page (you'll be able to find the part if you scroll down) this way:

    Also, the Bolt framework renders a simple web page for /slack/install with its default settings. Probably, it's not necessary in this case. You can set install_page_rendering_enabled=False in the App constructor (or write your own method to set anything else as necessary). see also: #183 #186

  5. ipl31 commented on Feb 26, 2021

    @ipl31
    Author

    @seratch thanks. That clears things up for me. Is it possible to support both scenarios. For example /slack/install or whatever install_path is set with does not render the page, but I setup a second route (maybe /slack/install_page) that will generate the HTML and button?

  6. seratch commented on Feb 26, 2021

    @seratch
    Contributor

    @ipl31

    For example /slack/install or whatever install_path is set with does not render the page, but I setup a second route (maybe /slack/install_page) that will generate the HTML and button?

    hmm, one possible way to do this is to have multiple App instances with shared OAuth settings. I haven't verified if the following app works yet but doing like this is an option.

    Update: install_page_rendering_enabled is part of OAuthSettings. If you use the LambdaS3OAuthFlow, you can modify the flag like this:

    app = App(
        process_before_response=True,
        oauth_flow=LambdaS3OAuthFlow(),
    )
    app.oauth_flow.settings.install_page_rendering_enabled = True
    
    direct_install_app = App(
        process_before_response=True,
        oauth_flow=LambdaS3OAuthFlow(),
    )
    direct_install_app.oauth_flow.settings.install_page_rendering_enabled = False

    If you use Flask and whatever similar, you may want to construct and pass OAuthSettings directly to App constructor.

    from slack_bolt import App
    from slack_bolt.oauth.oauth_settings import OAuthSettings
    
    # This App will be used for handling event requests from Slack and
    # /slack/oauth_redirect for both /slack/install and /slack/install_page
    app = App(
        # Use this settings except the flag
        oauth_settings=OAuthSettings(
            ...,
            install_page_rendering_enabled=True,
        )
    )
    
    # You don't need to have this method as long as you don't need to register the same set of listeners
    # to multiple App instances though
    register_listeners(app)
    
    def register_listeners(app: App)
        @app.event("app_mention")
        def handle_app_mention_events():
            pass
    
    
    # If you prefer other framework, you can go with another one
    from slack_bolt.adapter.flask import SlackRequestHandler
    from flask import Flask, request
    
    flask_app = Flask(__name__)
    
    default_handler = SlackRequestHandler(app)
    
    @flask_app.route("/slack/events", methods=["POST"])
    def events():
        return default_handler.handle(request)
    
    @flask_app.route("/slack/install", methods=["GET"])
    def install():
        return default_handler.handle(request)
    
    @flask_app.route("/slack/oauth_redirect", methods=["GET"])
    def oauth_redirect():
        return default_handler.handle(request)
    
    # This app should use the same OAuth settings with the default app
    app_direct_install = App(
        # Use this settings except the flag
        oauth_settings=OAuthSettings(
            ...,
            install_page_rendering_enabled=False,
        )
    )
    direct_install_handler = SlackRequestHandler(app_direct_install)
    
    @flask_app.route("/slack/install_page", methods=["GET"])
    def install_page():
        return direct_install_handler.handle(request)

    Hope this is helpful to you. This question and its answers should be helpful for others too! Thanks for taking the time to ask this 😄

  7. ipl31 commented on Feb 26, 2021

    @ipl31
    Author

    @seratch thanks for the idea! One last thing :) In your comment about install_page_rendering_enabled you mentioned:

    You can set install_page_rendering_enabled=False in the App constructor (or write your own method to set anything else as necessary).

    Setting it in the App constructor did not work for me. Did you mean in the OAuthSettings constructor? I am going to try creating a OAuthSettings object (using env vars currently so not creating one) and passing that when I create the LambdaS3OAuthFlow() object.

  8. ipl31 commented on Feb 26, 2021

    @ipl31
    Author

    Setting in OAuthSettings worked for me. Thanks again @seratch !

  9. seratch commented on Feb 26, 2021

    @seratch
    Contributor

    Oops, sorry about the error in my example! Glad to hear I could help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions