-
Notifications
You must be signed in to change notification settings - Fork 111
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
Use Redis session store #474
Conversation
650ce0c
to
4324a0f
Compare
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.
One issue that this change exposes is that when the session is removed from redis, when the user signs back in, they will see "Oops. Something went wrong. Please sign in again" due to this.
I believe this used to be the case before, except we didn't see it because we didn't clean up the expired sessions right away.
I'm not sure if this is a bug or if this is how the authenticity token works. Either way, we should fix this because it's a poor user experience to have to essentially sign in twice every time your session expires and is cleaned up.
key: '_upaya_session', | ||
redis: { | ||
driver: :hiredis, | ||
expire_after: Figaro.env.session_timeout_in!.to_i.minutes, |
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.
We prefer requiring Figaro keys proactively via config/initializer/figaro.rb
. session_timeout_in
is already defined there as a required key, so we don't need the !
here. Same with domain_name
below.
driver: :hiredis, | ||
expire_after: Figaro.env.session_timeout_in!.to_i.minutes, | ||
key_prefix: "#{Figaro.env.domain_name!}:session:", | ||
url: Figaro.env.redis_url! |
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.
Please add redis_url
to config/initializer/figaro.rb
(please keep the order alphabetical) instead of using the bang version.
}, | ||
# on_redis_down: ->(e, env, sid) { do_something_will_ya!(e) }, | ||
# on_session_load_error: ->(e, sid) { do_something_will_ya!(e) }, | ||
serializer: :marshal |
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.
Why Marshal and not JSON? JSON is the default in Rails 4+, and what we are currently using for cookies. See cookies_serializer.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.
- Marshal is what the ActiveRecord session store is using now
- Marshal might be slightly faster than JSON
- We don't store our sessions in cookies, so
config/initializers/cookies_serializer.rb
may be ignored?
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.
Ah, yes. I was confusing the two.
What is the status on this pull request? Looks like @monfresh reviewd 19 days ago but there haven't been updates. Are we going to close and move in a different direction or is there a plan to update this? |
I believe we still need to complete this. @typesend can confirm. Ben, do you still have time to finish this, or should we assign it to someone else? @jessieay If I recall correctly, what is holding this up is this failing test: https://github.com/18F/identity-idp/blob/master/spec/features/users/sign_in_spec.rb#L126-L149 @pkarman, @typesend and I had an in-person discussion about this, and we think the solution is to delete the session cookie whenever the session is removed from Redis. |
Paging @typesend. |
Go ahead and reassign. I have too many things on my plate. :/
If there isn't already a ticket for encrypting redis / elasticache session
data we need that as well per DHS' requirement (because elasticache
infrastructure not considered as secure/isolated as other aws services.)
|
encryption of session already prepped in #516 |
for reference: the failing test is due to http://stackoverflow.com/a/7744647 -- invalid authenticity token error, because the (temp, un-authenticated) session has expired. The CSRF token is stored in the session on the server side, and compared against the incoming request to verify they match. This was not a problem with the ActiveRecord session store, for a bad reason: we currently don't auto-delete the ActiveRecord sessions when they expire. That means the CSRF check succeeds, even though the session is expired. We can skip the CSRF check for the session create action only, where it is least needed (since the user is not yet authenticated, cross-site attacks aren't effective). index 2f6b9ae..a01803f 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -3,6 +3,7 @@ module Users
include ::ActionView::Helpers::DateHelper
skip_before_action :session_expires_at, only: [:active]
+ skip_before_action :verify_authenticity_token, only: [:create]
def create
track_authentication_attempt(params[:user][:email]) |
8844a75
to
82f7ffb
Compare
I'm going to remove the WIP from this so we can get it reviewed. However, I'm also going to mark it DO NOT MERGE until we can resolve the session encryption PR in #516. |
Added the session timeout modal separately in this PR https://github.com/18F/identity-idp/pull/578/files |
cb14936
to
8dd8765
Compare
8dd8765
to
0079ee5
Compare
I'm marking this as ready to review and merge. |
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.
👏 just two tiny comments from me
key_prefix: "#{Figaro.env.domain_name}:session:", | ||
url: Figaro.env.redis_url | ||
}, | ||
# on_redis_down: ->(e, env, sid) { do_something_will_ya!(e) }, |
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.
what are these commented line about?
def update_session_ttl(sid) | ||
expiry = default_options[:expire_after] | ||
redis.expire(prefixed(sid), expiry) | ||
rescue Errno::ECONNREFUSED, Redis::CannotConnectError => error |
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.
do we need / should we have a test for this?
key: '_upaya_session', | ||
redis: { | ||
driver: :hiredis, | ||
expire_after: Figaro.env.session_timeout_in_minutes.to_i, |
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.
Looks like you accidentally removed the .minutes
from the end here? I have this branch locally and it has the .minutes
at the end. Otherwise, won't it expire every 8 seconds?
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.
Or maybe I added it in my local branch when I realized the bug 😄
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.
Given how often we make this mistake, I wonder if we should perhaps use seconds for the Figaro setting, but that would be another PR.
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.
I just updated my PR to use Devise.timeout_in
so that we are dealing with an integer rather than a string. Should we do that here, too? https://github.com/18F/identity-idp/pull/596/files
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.
I think config files relying on one another for initialization order can get messy fast. I'd prefer to always pull from Figaro.
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.
good call. I like the idea of having the .to_i.minutes
call being in one place -- but perhaps not tenable
expiry = default_options[:expire_after] | ||
redis.expire(prefixed(sid), expiry) | ||
rescue Errno::ECONNREFUSED, Redis::CannotConnectError => error | ||
on_redis_down.call(error, env, sid) if on_redis_down |
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.
Given that the on_redis_down
option is not set in our session store config, this won't do anything. So, we should either get rid of this section, or actually configure on_redis_down
to do something, and in that case, we can get rid of if on_redis_down
because it will be present.
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 will also address the reek/rubocop issues.
on_redis_down.call(error, env, sid) if on_redis_down | ||
return false | ||
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.
Do we know why we even need this monkey patch? Doesn't the gem expire sessions already based on the expire_after
option? I will test now.
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.
I have no idea. I was just going off what @typesend had written.
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.
Looks like the gem works as expected out of the box. Here's how I tested:
- I made the session store look like this:
options = {
key: '_upaya_session',
redis: {
driver: :hiredis,
expire_after: Figaro.env.session_timeout_in_minutes.to_i.minutes,
key_prefix: "#{Figaro.env.domain_name}:session:",
url: Figaro.env.redis_url
},
# on_redis_down: ->(e, env, sid) { do_something_will_ya!(e) },
# on_session_load_error: ->(e, sid) { do_something_will_ya!(e) },
serializer: :marshal
}
Rails.application.config.session_store :redis_session_store, options
redis-cli flushall
bin/setup
- Set
Figaro.session_timeout_in_minutes
to1
foreman start
- Visit localhost:3000
- Open a Redis console with
redis-cli
, select the "0" database,select 0
, and look at all the keys:keys *
. - Verify there is a key that starts with
localhost:3000:session
. - Wait 1 minute
- run
keys *
again and verify the key is gone
Also tested signing in and letting session time out on its own. When the user is signed out, the previous session they had is removed from Redis, and then they immediately get a new session. If they wait another minute, that session gets removed as well and no new sessions will appear in Redis until a new request is made.
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.
I also tested that the expiry time automatically resets every time a new request is made. So, if a user signs in at 12:00:00, then makes their last request 30 seconds later, the session times out at 12:01:30, not 12:01:00.
9ec0e86
to
f4b5b32
Compare
@@ -1 +1,14 @@ | |||
Rails.application.config.session_store :active_record_store, key: '_upaya_session' | |||
require 'idle_session_expiration_store' |
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.
Please zap this too.
1904181
to
0232acf
Compare
**Why**: * speed of session access * auto-deletion of expired sessions * separation of data stores (db and session)
2433b89
to
9356d69
Compare
ok @monfresh this should be good to go |
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
See 18F/identity-private#785