-
Notifications
You must be signed in to change notification settings - Fork 641
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
allow single_access_token via http headers #728
base: master
Are you sure you want to change the base?
Conversation
Hi Scott, thanks for the contribution. Before we review this, can you please remind me of some of the security considerations re: putting plaintext secrets in HTTP headers? My dim recollection is there's no difference compared to e.g. a URL param, ie. the security of either approach depends on TLS and SNI? Can you please summarize the concerns and/or point me to some reading material? Thanks. |
Thank you for your quick reply @jaredbeck ! I believe your recollection is exactly correct. And, in fact, OAuth generally uses a Header for the From https://en.wikipedia.org/wiki/HTTPS:
I had trouble finding such a clear statement from the RFC, but it basically says that after the initial connection, ALL data must be sent as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Scott, I've finished my first review. It looks like you've copied all the params methods (and tests) carefully, so it all looks good at first glance. I'd like to address the level of duplication first. Once we've addressed that, I think a final review will go smoothly. Thanks!
lib/authlogic/session/base.rb
Outdated
else | ||
%i[all any].include?(single_access_allowed_request_types) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems headers_enabled_by_allowed_request_types?
and params_enabled_by_allowed_request_types?
have exactly the same contents. Let's fix that duplication by having a single method. What do you think about a name like request_type_allowed?
, or single_access_token_allowed_by_request_type?
?
test/session_test/headers_test.rb
Outdated
UserSession.single_access_allowed_request_types | ||
) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid duplicating ConfigTest
verbatim, perhaps we should combine params_test.rb
and headers_test.rb
and call it single_access_token_test.rb
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jaredbeck Note that I don't necessarily like adding a method that isn't really a test_
method to the class SessionTest::SingleAccessTokenTest::InstanceMethodsTest
but it was the best way I could see to reduce the duplication, and not hide the actual assertions off in a helper file (which would be far worse for the reader).
Overall, I realize I totally missed DRY the first time -- not a novice programmer, but new to submitting PRs to open source. Since this was security, I was a bit afraid to make any of these slight refactors without having a review by someone much more familiar with the code. Thank you for your patience! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than my suggestion about maybe using Set
, this LGTM.
Please add an item to the changelog under Unreleased -> Added.
@tiegz can you also take a quick look at this PR, please?
Co-authored-by: Jared Beck <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a useful addition to Authlogic, @sevgibson .
Only concern I have is: is this a breaking change if it's enabled by default?
Downstream users should already be aware that url-params can authenticate, if they have the single_access_token
column. Will it be surprising then that someone could authenticate via a header suddenly too? (i.e. could single-access-token-replay attacks suddenly become easier to conceal?)
The answer might be "no", just curious what you all think.
Thanks for the review, Tieg.
It's not a breaking change as far as our API is concerned, and we haven't yet identified any meaningful impact on security. I'm glad you mentioned replay attacks, though. Can you talk a bit more about that? An attacker intercepts the PS: I find the term "single access" misleading, because it sounds like "single use", but that's probably a discussion for another time. |
Co-authored-by: Tieg Zaharia <[email protected]>
Co-authored-by: Tieg Zaharia <[email protected]>
@tiegz @jaredbeck I agree that this should not be enabled by default. This makes perfect sense to me because my intent was not to make a change that would become active by default for existing apps. If you both agree, then I'll submit an update to default to |
💯 |
Scenario: a user's single access token is accidentally leaked.
That's my only worry -- but it assumes things about logs and importance of params vs headers, so maybe it's NBD.
Not a bad idea TBH!
Totally agreed. At a past job, I created an Authlogic extension that wrapped database-backed tokens and used the |
I personally prefer that to avoid any surprises, but it's up to you @jaredbeck (and then maybe we could even enable it by default in the next major version?) |
Also, wanted to mention an experience I've had in the past: I was using something very similar to single_access_token for authentication in the url. We discovered that the url was being cached in Facebook's caching layer because it was triggering 2FA SMS codes spuriously for users. Given that, plus how often people forget to scrub params in logs, it's worth considering how easy it is to leak a single_access_token [in url params]. |
And another experience I just remembered 😆 : Got hit by a credential-stuffing attack where the attacker was using Basic Auth to attempt mass logins. Luckily we had disabled Authlogic's Basic Auth (which I think was disabled as a default years ago too). But since we weren't logging the Basic Auth header anywhere, the only way I could figure that out was accidentally while looking at raw requests 🤮 [Lesson from that being: headers are less obvious than params] |
@tiegz I appreciate how thoroughly you're considering the security implications, since of course most security holes (aside from intentional trojan horse) are due to holes that are simply overlooked. Having said that, I think that having this disabled by default leaves the assessment of the risks specifically related to using TBH, using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor stuff. I think we're close.
Co-authored-by: Jared Beck <[email protected]>
Co-authored-by: Jared Beck <[email protected]>
@jaredbeck I had to fix specs, and also decided to explicitly assert the defaults for both |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. @tiegz please merge (squash) if you're happy
Looks really close to me, but I can see 2 blockers:
|
@tiegz Ok, makes sense -- this may take me a little time, so marking this as draft for now, and will request review again when both are done. Thanks, both for working through this PR with me! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sevgibson I've added PR suggestions for the 2 fixes I mentioned. I've tested this locally and it fixes the feature for me.
# Setting headers_key to nil is the accepted way to disable | ||
# single_access_token in headers. | ||
return nil if headers_key.nil? | ||
controller.headers[headers_key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
controller.headers[headers_key] | |
controller.request.headers[headers_key] |
@@ -269,6 +269,14 @@ def unset_params | |||
controller.params["user_credentials"] = nil | |||
end | |||
|
|||
def set_headers_for(user) | |||
controller.headers["user_credentials"] = user.single_access_token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
controller.headers["user_credentials"] = user.single_access_token | |
controller.request.headers["user_credentials"] = user.single_access_token |
end | ||
|
||
def unset_headers | ||
controller.headers["user_credentials"] = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
controller.headers["user_credentials"] = nil | |
controller.request.headers["user_credentials"] = nil |
def headers | ||
controller.headers | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def headers | |
controller.headers | |
end |
@@ -1849,6 +1894,10 @@ def params_key | |||
build_key(self.class.params_key) | |||
end | |||
|
|||
def headers_key | |||
build_key(self.class.headers_key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
build_key(self.class.headers_key) | |
# Rack servers will uppercase header names, convert hyphens to undercsores, and prefix with "HTTP_" in order | |
# to comply with CGI. We translate these converted headers in a similar way to Rails: | |
# https://github.com/rails/rails/blob/6-0-stable/actionpack/lib/action_dispatch/http/headers.rb#L123-L126 | |
"HTTP_" + build_key(self.class.headers_key).upcase.tr("-", "_") |
Acts exactly like
params
, but usesheaders
instead.