slackapi / bolt-python

A framework to build Slack apps using Python
https://tools.slack.dev/bolt-python/
MIT License
1.07k stars 246 forks source link

Is it possible to handle OAuth flow entirely with websockets? #583

Closed nimjor closed 2 years ago

nimjor commented 2 years ago

Our team has an app that currently uses Flask and the OAuth flow to allow our users to set up incoming webhooks in a self-service fashion. We are trying to rewrite the app to use Socket Mode. Using OAuth flow seems to require a redirect URL, but the redirect URL requires HTTP/S. We verified that the SocketModeHandler client's wss_uri is not accepted as the redirect URL. Is there a way to complete the OAuth flow without exposing some HTTP endpoint? It seems almost everything can be done except for passing the generated authorization code back to the socket mode app. If we had a way to get that code, it seems we would be able to use app.client.oauth_v2_access to finish the flow.

Reproducible in:

The slack_bolt version

slack-bolt==1.6.1 slack-sdk==3.6.0 slackclient==2.5.0

Python runtime version

Python 3.9.9

OS info

ProductName: macOS ProductVersion: 11.6.2 BuildVersion: 20G314 Darwin Kernel Version 20.6.0: Wed Nov 10 22:23:07 PST 2021; root:xnu-7195.141.14~1/RELEASE_X86_64

Steps to reproduce:

There is nothing to reproduce as this is a question about whether the above is possible. In all our searching in the docs and others' questions we have not found an answer.

Expected result:

Bolt OAuth functionality works regardless of connection type.

Actual result:

There appears to be no way to have the websocket-based Slack app receive the generated OAuth authorization code upon user authorization.

Requirements

An answer to this question would be appreciated; a method/example for completing the OAuth flow with websocket connections would be even better.

srajiang commented 2 years ago

@nimjor - Here is a sample app setup which uses SocketMode and implements Oauth. Is this helpful? https://github.com/slackapi/bolt-python/blob/main/examples/socket_mode_oauth.py

nimjor commented 2 years ago

Thanks @srajiang. We have tried implementing OAuthSettings already. That example doesn't appear to do anything different. The main problem, which that example doesn't address, is what to do for a redirect url to which the data is sent at the end of the OAuth flow, in particular the code parameter.

This is pretty much the exact solution we are using currently, which we are trying to move to strictly socket mode: https://api.slack.com/messaging/webhooks#incoming_webhooks_programmatic

The closest we've gotten to the desired functionality is by setting the redirect url to the web api endpoint for oauth.v2.access, as in the screenshot. By doing that and including client_id and client_secret query parameters, the flow completes successfully.

Redirect URL:

IMG_0241

Success showing newly created webhook (JSON pretty-printed with browser plugin):

IMG_0239

But this is a very hacky workaround. And it doesn't seem secure to pass the client_secret as a URL param? Also the success page in this case is not user-friendly, and it exposes the bot access token in the JSON.

@srajiang or @seratch any guidance on the proper way to complete the OAuth flow exclusively with websockets would be great, ideally you'd allow for redirecting to a websocket host so that our app can get that OAuth authorization code, pass it to oauth.v2.access within the app/server so that the app can then update the modal that started this flow to show the user the webhook and curl example.

seratch commented 2 years ago

Hi @nimjor,

any guidance on the proper way to complete the OAuth flow exclusively with websockets would be great, ideally you'd allow for redirecting to a websocket host so that our app can get that OAuth authorization code, pass it to oauth.v2.access within the app/server so that the app can then update the modal that started this flow to show the user the webhook and curl example.

Unfortunately, there is no way to do this properly. As @srajiang suggested, the only way that we can recommend is to ask your end-users to go through the OAuth flow in their web browser.

I hope this helps.

nimjor commented 2 years ago

Hi, So you're saying there's still a way end users can complete the required OAuth flow to generate a new incoming-webhook?

I tried building a test app using the code sample @srajiang provided but I haven't been able to figure out what's needed for the "installation store". The app starts up fine, and I created a shortcut with a callback_id "socket-mode", but when I trigger that shortcut from the Slack client, bolt throws an error about missing installation store directory. Makes sense since it isn't defined in OAuthSettings in that sample code. I created the missing directories and created the installer-latest and bot-latest files that it seems to be looking for (created them as empty files first). It doesn't like when these files are empty, and I can tell it is looking for JSON formatted text in them, so I tried to find what the format of these should be so I can try creating them myself, but I haven't been able to discover this in the docs, and it seems like something that should be happening as part of the program (not something the developer should manually write?).

Not sure if that code sample will even help with this process, if I can the installation store element figured out. But if you can explain how end users can still do the OAuth flow through the browser and get their newly generated incoming webhook url at the end of the flow, that would be helpful.

seratch commented 2 years ago

@nimjor

So you're saying there's still a way end users can complete the required OAuth flow to generate a new incoming-webhook?

Yes, the example @srajiang mentioned is the way to do so. Your app uses a WebSocket connection for receiving/sending event data from/to Slack. Separately, the same app can serve URLs (this is not WebSocket based) for the OAuth interactions with installing users.

I haven't been able to figure out what's needed for the "installation store". The app starts up fine, and I created a shortcut with a callback_id "socket-mode", but when I trigger that shortcut from the Slack client, bolt throws an error about missing installation store directory.

This is because you haven't installed the app through the OAuth flow, which starts from /slack/install URL. Once you complete the OAuth flow, bolt-python saves the tokens in InstallationStore (the default one is local file based one - FileInstallationStore). You should not manually create the files. Just go through the OAuth process with sufficient scopes / user_scopes. To learn the OAuth flow in general, checking our documents like this one may be helpful.

But if you can explain how end users can still do the OAuth flow through the browser and get their newly generated incoming webhook url at the end of the flow, that would be helpful.

I'm still unsure why you would like to use Incoming Webhooks rather than chat.postMessage API method in this scenario but as long as you have incoming-webhooks in the scopes of your OAuth flow, the installation data managed by InstallationStore includes the webhook URL. Your app can use the URL anywhere in your app.

nimjor commented 2 years ago

The reason we are using incoming webhooks instead of chat.postMessage is because this bot's purpose is to allow our users to create incoming webhooks in a self-service fashion, without requiring us (the admin team) to be involved. They simply use a shortcut that opens a modal displaying 3 options/buttons, they click their option which starts the OAuth flow, and when they authorize the app, it redirects them to our /slack/oauth route which passes the OAuth temporary authorization code as a query param, which our app then uses in a POST to slack.com/api/oauth.v2.access, and when it gets back a success message, it also gets the webhook URL which it then uses to update the original modal, displaying the webhook and a curl example for the user. FYI, we are essentially following this guide https://api.slack.com/messaging/webhooks#incoming_webhooks_programmatic.

We have a number of other apps/bots that we've successfully moved to only socket-mode. Our organization's security doesn't like that we have to allow incoming HTTPS traffic from basically anywhere in order to make these work, so we've historically used a reverse proxy in front of all those bots. This one is the last one to get switched over to socket mode so that we can retire that proxy, but from the sound of it, it's not possible. We either have to sacrifice the self-service incoming-webhooks, or keep exposing at least one HTTPS endpoint to support the end of the OAuth flow (where the client is redirected to our endpoint, thereby passing us the authorization code).

I don't think there's anything more you can do for me here, but just want to make sure its said that it would be fantastic if you could at some point either: (A) enable support for websocket connections at the redirect step, or (B) provide IP addresses/ranges so that we can limit our exposure. Thanks for taking the time on this question!

srajiang commented 2 years ago

@nimjor - Really appreciate you taking the time to submit this feedback and feature enhancement request. It helps when folks from the community help to get desired functionality onto our radar. 🙇‍♀️

While I don't know whether this is something we can prioritize in upcoming release, I have gone ahead and marked this as an enhancement request so that it may be surfaced in future planning cycles.

rcsrao commented 11 months ago

Hello, we are also with the similar requirement. Our app is in socket mode and we will need to use OAuth method. Any help will be highly appreciated.

@srajiang

@nimjor Were you able to figure any luck on this?

Maansy commented 9 months ago

I have the same problem, and after alot of researches. I think no way to access the callback uri using a websocket.

So if anyone find a solution for this problem please mention me. Thanks. @srajiang @rcsrao @objectfox @nimjor @seratch

nickovs commented 6 months ago

I have the same issue. Socket Mode is a huge win from a security standpoint since we don't have to configure externally accessible ports on the machine hosting the service. Having to expose a callback endpoint breaks that.

It occurs to me that one solution is for Slack themselves to host a redirect forwarder somewhere on their API servers. Instead of the app having to expose a callback endpoint, you could use https://slack.com/api/oauth_redirect/MY_APP_ID; when that endpoint is hit, the payload would get some cursory sanity check and then be passed back to the Socket Mode app as an event. This would allow third party OAuth2 MFA providers, SSO systems and identity federation systems to continue to work without change and still allow Socket Mode apps not to have to publish a whole separate endpoint just to support user authentication.

nickovs commented 6 months ago

@srajiang Just to add another use case for why this is valuable:

You can run a simple SocketMode app inside an AWS EC2 container very easily. As soon as you have to open up an HTTPS port with a valid certificate (which is needed for the callback URI), you open up a whole world of pain. Most of the free certificate providers won't issue certificates for FQDNs inside the amazonaws.com domain and Amazon Certificate Manager won't issue certificates for endpoint machines, only for their load balancing fronting services, so rather than just starting up a Python program, you now have to configure a whole stack of AWS services. If Slack provided a mechanism to have an App-specific endpoint and forward the responses back down the open web socket all these problems would go away!