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

Make authenticity token length a fixed value #1181

Merged
merged 1 commit into from
Dec 25, 2016

Conversation

jkowens
Copy link
Member

@jkowens jkowens commented Sep 10, 2016

Adds a new configurable option :csrf_key that allows the csrf session key to be configured to be shared with other frameworks.

For instance it could be changed to :_csrf_token to be compatible with Rails. This way Sinatra forms could be sent to Rails actions and pass CSRF protection and vice versa (as long as the session is shared).

use Rack::Protection, :csrf_key => :_csrf_token

@zzak let me know if you think any option names or method names need changed. I also tried to reduce the public API a bit in this update.

@jkowens jkowens force-pushed the token_regression branch 5 times, most recently from fdd474d to 65699c4 Compare September 11, 2016 15:39
@zzak
Copy link
Member

zzak commented Sep 12, 2016

Ugh, sorry haven't had time to look into this issue lately.

How come you're using class variables instead of default options?

Re naming, we already have authenticity_param, why not authenticity_key?

Also just as a reflex, this patch is really hard to grok by the diff because there's a lot of changing of method visibility. So please give me some time to review this more thoroughly!

Thank you for your patch!!

@jkowens
Copy link
Member Author

jkowens commented Sep 12, 2016

Yeah, sorry about big diff.

The reason for the class variables for the session key and token length stems from the need to offer a way get a masked authenticity token when building forms in a view. So you could do that now by calling:

Rack::Protection::AuthenticityToken.token(session)

I think most would want to put that in a helper.

Previously you would just use the value session[:csrf], and you could still do that but you wouldn't get BREACH protection.

To avoid using class variables, there are a couple options I can think of:

  1. The session key and token length could be passed in along with the session as options. (seems kind of painful to have to check the middleware configuration when building forms).
  2. Keep the session key a fixed value of :csrf and token length a fixed value of 32. If a Rails app needs to generate a form to a mounted Sinatra app it could use Rack::Protection::AuthenticityToken.token(session) as the value for the authenticity token (instead of using the standard view helper). If a Sinatra app needs to post to a Rails form it could use a method like Rack::Protection::AuthenticityToken.mask_token(session[:_csrf_token]). A little more involved, but maybe this is the right way for a little used scenario?

@jkowens jkowens force-pushed the token_regression branch 2 times, most recently from 16ba3f4 to 589c5e2 Compare September 15, 2016 00:25
@zzak
Copy link
Member

zzak commented Sep 17, 2016

So I guess, prior to Rails adding BREACH mitigation, the tokens were compatible. And since then we've been promising a lie, because they were no longer compatible. I did however see something about keeping support for existing session tokens with the Rails patch, but I'm not sure if that affects us.

If that's true (P.D.I.), and we're both using different BREACH strategies (e.g. the token length is different, etc) then I would almost say let's just use the major version bump to break that promise and update the documentation for Sinatra. I have a feeling supporting shared sessions between sinatra and Rails will prove to be an ongoing effort when it is unclear how many people rely on this feature, and could implement it themselves.

@jkowens
Copy link
Member Author

jkowens commented Sep 18, 2016

Yes, once Rails started using masked tokens in forms, rack-protection would not have been able to handle them. You're right though, Rails would still be able to compare the unmasked authenticity tokens so a Sinatra app could directly access session[_:csrf_token] and that would work.

I think using the major version bump to break the promise of compatibility is good. Or at least the idea of syncing the values of the two different authenticity tokens stored in the session. I've updated the PR to use a fixed value for the session key and token length. This makes the helper method for fetching a masked version for the token easy to use:

Rack::Protection::AuthenticityToken.token(session)

Since the BREACH protection strategy and token length does currently match up with Rails the following code could be used to get a masked version of the Rails authenticity token:

Rack::Protection::AuthenticityToken.token(session, :_csrf_token)

If you don't think it's a good idea to go down that road though, I could remove that optional parameter.

@zzak
Copy link
Member

zzak commented Sep 18, 2016

I'm almost 90% sure we should just break the promise, but I'd like to get some feedback to see if anyone is actually using this feature. Maybe @rkh knows?

Sinatra isn't designed to be a middleware, it has it's own router and other utilities that make it set apart from the rest of your stack, be it Rails or whatever. However, RP is designed to be a middleware, but at the core of it is session protection. We can't possibly hope to maintain compatibility with every other session protection out there, so in my opinion if you want to protect a session you should decide before hand how that session will be shared or not -- and which protection library to use.

@jkowens
Copy link
Member Author

jkowens commented Sep 19, 2016

@zzak I'm all for that suggestion 👍

I do think I'd still rather see the token length a fixed value of 32. It keeps things simple and it makes sure a secure value is used. See: http://security.stackexchange.com/questions/6957/length-of-csrf-token

Also, this way Rack::Protection::AuthenticityToken.token(session) can be used by other apps that do share the session. It will make sure a token is set in the session and return the masked value (without worrying about token length).

If that's good, I'll clean up these commits.

@zzak
Copy link
Member

zzak commented Sep 20, 2016

I do think I'd still rather see the token length a fixed value of 32.

👍

@jkowens jkowens force-pushed the token_regression branch 3 times, most recently from 33a915d to 0709f8e Compare September 20, 2016 03:15
Rack::Protection::AuthenticityToken.token(session) can now make sure that
an authenticity token is set in the session before returning a masked
version of the token. Any Rack app that shares the session can use this
method when building a form for an app that uses rack-protection.

Removes references to Rails compatibility as rack-protection 2.0
officially breaks that promise.
@jkowens jkowens changed the title Allow authenticity token compatibility with Rails Make authenticity token length a fixed value Sep 20, 2016
@zzak zzak added this to the 2.0.0 milestone Dec 25, 2016
@zzak zzak added the bug label Dec 25, 2016
@zzak zzak merged commit bf2f65a into sinatra:master Dec 25, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants