-
Notifications
You must be signed in to change notification settings - Fork 402
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
Installation flow fails with slack_oauth_invalid_state when running an app on Firebase Hosting #1730
Comments
Hi @multimanic, thanks for asking the question! We don't officially support the runtime, so we are not able to help you out on how to deploy apps onto Firebase Hosting. That aside, possible causes of your issue can be:
I hope this was helpful to you. |
Thank you so much @seratch for unblocking me after 4 days of desperation. I also had a hunch that the state verification is the issue. However, I can see all the cookies set and sent on firebase like on ngrok. |
First off, the ideal solution is identifying the cause of your situation with Firebase and keeping the state verification on. Generally speaking, I don't recommend disabling it. With that being said, if you think the risk is acceptable for your app after going through this reply, it's your decision.
The security risk is not specific to Slack's OAuth flow. It is a general CSRF attack risk for your end users who install your app. Refer to the following resources:
As mentioned in the above resources, when an attacker navigates your end user to a redirect URL with the attacker's auth code parameter and a valid state, your end user may complete their OAuth flow with an unexpected association. So, verifying the state through the end-to-end OAuth flow is a highly recommended security measure for apps providing OAuth flows. That's why our SDKs enable the verification by default. As long as you are aware of the risk with the lack of state parameter, you can go ahead with disabling it. If you don't offer any account mapping feature like associating a Slack workspace/user with your external service account, the risk should be quite limited. Otherwise, we highly recommend implementing something that ensures the same user visiting both
No, actually. I hope this comment answers your questions. If everything is clear now, would you mind closing this issue? |
Thanks @seratch and I agree, I should fix this. Here is my understanding of the flow ad the code:
That said, I tried to compare the network trace and the cookies of my ngrok and my host. They are exactly the same.
followed by this sequence
finally the page
includes a request header
The state value matches in all URL parameters and slack-app-oauth-state cookies but it still fails with the InvalidStateError. I'm puzzled. |
@seratch can you confirm that my understanding of the code is correct? |
@multimanic I am still unsure about the cause of your issue here, but one possibility is that your app on Firebase Hosting platform may be failing to access the request header data including cookies. Adding some print debug logs like |
Your instinct is good. I just found https://firebase.google.com/docs/hosting/manage-cache#using_cookies says
I will test what happens if I use |
SOLUTION - SOLUTION - SOLUTION - SOLUTION - SOLUTION It is possible to use Firebase Cloud Functions with Firebase Hosting with distributed apps (using Bolt JS's Example: const config = require('./config/config');
const functions = require('firebase-functions');
const firebase = require('firebase-admin');
const { App, ExpressReceiver } = require('@slack/bolt');
const { LogLevel } = require("@slack/logger");
const firebaseApp = firebase.initializeApp(
functions.config().firebase
);
const db = firebaseApp.firestore();
firebase.firestore().settings({
ignoreUndefinedProperties: true,
})
const expressReceiver = new ExpressReceiver({
logLevel: LogLevel.DEBUG,
signingSecret: config.slack.signingSecret,
clientId: config.slack.clientId,
clientSecret: config.slack.clientSecret,
stateSecret: config.slack.stateSecret,
scopes: config.slack.scopes,
endpoints: '/slack/events',
processBeforeResponse: true,
installationStore: {
storeInstallation: async (installation) => {
if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
return await db.collection(config.firebase.slackTeams).doc(installation.enterprise.id).set(installation);
}
if (installation.team !== undefined) {
return await db.collection(config.firebase.slackTeams).doc(installation.team.id).set(installation);
}
throw new Error('Failed saving installation data to Firebase');
},
fetchInstallation: async (installQuery) => {
if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
return await getTeamRecord(installQuery.enterpriseId);
}
if (installQuery.teamId !== undefined) {
return await getTeamRecord(installQuery.teamId);
}
throw new Error('Failed fetching installQuery');
},
deleteInstallation: async (installQuery) => {
if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
return await db.collection(config.firebase.slackTeams).doc(installQuery.enterpriseId).delete();
}
if (installQuery.teamId !== undefined) {
return await db.collection(config.firebase.slackTeams).doc(installQuery.teamId).delete();
}
throw new Error('Failed to delete installQuery');
},
},
installerOptions: {
directInstall: false,
stateVerification: true,
stateCookieName: '__session',
}
});
const app = new App({
receiver: expressReceiver,
processBeforeResponse: true,
stateSecret: config.slack.stateSecret,
});
app.command('/say-hi', async ({ command, ack, say }) => {
await ack();
const { botToken } = await app.receiver.installer.authorize({teamId: command.team_id});
app.client.chat.postMessage({
token: botToken,
channel: command.user_id,
text: "Hi back!",
as_user: true,
});
});
exports.slack = functions.https.onRequest(expressReceiver.app); |
How wise of you @seratch to make the cookie name configurable :) |
Description
Describe your issue here.
I'm using
/slack/install
to install my distributed app to a test workspace. Everything works perfectly fine using ngrok but as soon as I deploy my app to my server the installation fails with the page/slack/oauth_redirect?code=...
sayingOops, Something Went Wrong!
Please try again or contact the app owner (reason: slack_oauth_invalid_state)
The server debug log says
My code is pretty much like this by @seratch but using Firestore as database (again, everything works perfectly fine with ngrok).
What type of issue is this? (place an
x
in one of the[ ]
)Requirements (place an
x
in each of the[ ]
)Bug Report
Filling out the following details about bugs will help us solve your issue sooner.
Reproducible in:
package version: @slack/bolt 3.12.2 and @slack/oauth 2.6.0
node version: 18
OS version(s): Google Firebase
Steps to reproduce:
Thank you!
Redirecting to the Slack App... click [here]. If you use the browser version of Slack, click this link instead.
Oops, Something Went Wrong!
Please try again or contact the app owner (reason: slack_oauth_invalid_state)
Expected result:
Installation should succeed.
Actual result:
Installation flow fails with slack_oauth_invalid_state and a debug log
[ERROR] OAuth:InstallProvider:0 Error: The state parameter is not for this browser session.
Attachments:
Logs, screenshots, screencast, sample project, funny gif, etc.
The text was updated successfully, but these errors were encountered: