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

Verify Slack OAuth State #264

Merged
merged 15 commits into from
Feb 16, 2018
Merged

Verify Slack OAuth State #264

merged 15 commits into from
Feb 16, 2018

Conversation

wilhelmklopp
Copy link
Contributor

@wilhelmklopp wilhelmklopp commented Feb 6, 2018

This PR implements use of the state parameter in the slack OAuth flow to protect from CSRF attacks. More details about the "why" here

Fixes #257
Fixes #254

@codecov
Copy link

codecov bot commented Feb 6, 2018

Codecov Report

Merging #264 into master will increase coverage by 0.14%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #264      +/-   ##
==========================================
+ Coverage   97.27%   97.41%   +0.14%     
==========================================
  Files          76       76              
  Lines        1101     1122      +21     
  Branches      127      131       +4     
==========================================
+ Hits         1071     1093      +22     
+ Misses         28       27       -1     
  Partials        2        2
Impacted Files Coverage Δ
lib/slack/oauth.js 100% <100%> (+3.84%) ⬆️
lib/slack/index.js 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 22d12cb...07b116b. Read the comment docs.

@@ -1,7 +1,6 @@
const { exec } = require('child_process');
require('./env');
require('./nock');
require('./date');
Copy link
Contributor Author

@wilhelmklopp wilhelmklopp Feb 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately setting a global date that is in the past (introduced in #167) completely breaks auth-related cookies as they expire immediately. @bkeepers how do you feel about going with something like #166 instead to fix the problem of time dependent tests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, anything that is using Date.now() should work. I would expect the opposite, where it never expires.

I'm good with whatever works and doesn't break the tests every month.

Copy link
Contributor

@bkeepers bkeepers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the GitHub OAuth process, what would you think about using jwt instead of cookies?

https://github.com/github-slack/app/blob/master/lib/github/oauth.js#L43

@@ -1,66 +1,98 @@
const slack = require('./client');
const cryptoRandomString = require('crypto-random-string');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This package is pretty much one line of code. Can we do this manually to avoid unnecessary dependency?

https://github.com/sindresorhus/crypto-random-string/blob/master/index.js#L9

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing it 👍
FWIW it was already in our dependency tree, though.


if (!req.query.state) {
req.log.debug('No state param in callback.');
return res.status(400).send('No state param in callback. Please try again.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the proper resolution for a user that ends up in this position? Would it make more sense to redirect to /slack/oauth/login here to re-start the OAuth process?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think that's one potential option. Although I've seen buggy implementation of this end up in infinite loops where the user is constantly redirected, which is of course even more confusing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna give this a shot now and see if it works well :)


if (!req.session.slackOAuthState) {
req.log.debug('No state in session.');
return res.status(400).send('No state in session. Please try again.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here


if (!matches) {
req.log.debug('Session state does not match state.');
return res.status(400).send('Session state does not match state. Please try again.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here

@@ -1,7 +1,6 @@
const { exec } = require('child_process');
require('./env');
require('./nock');
require('./date');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, anything that is using Date.now() should work. I would expect the opposite, where it never expires.

I'm good with whatever works and doesn't break the tests every month.

@wilhelmklopp
Copy link
Contributor Author

wilhelmklopp commented Feb 14, 2018

For consistency with the GitHub OAuth process, what would you think about using jwt instead of cookies?

Interesting. So just a random value signed with the jwt representing the state?

I have a feeling that might still leave us open to the CSRF attack vector. An attacker could just kick off the oauth flow, copy the value that is signed by our JWT, and then send a victim into a new OAuth flow which we wouldn't be able to detect.

Thinking about it the reason why it's secure for GitHub OAuth is because we encode data in the state that's not really guessable:
https://github.com/github-slack/app/blob/master/lib/github/oauth.js#L43-L47

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

Successfully merging this pull request may close these issues.

2 participants