I am new to Rails and experience a strange issue I don't understand. I use ActiveRecord as a session store and need to add session id as a property of JSON responses for all the requests. I use Devise as well if it have some impact on the situation. The problem is that if a request is made by a user without cookies (or at least without session id in the cookie) the session.id is empty or - attention, please - not the same value that is set in the response cookie.
For debugging, I add this code as an after_filter to ApplicationController:
puts session.id
puts request.session_options[:id]
Both values are the same. They match the value in the cookie if it is present. Otherwise, if session id is not present in the cookie, the cookie set after that request has different value.
My opinion is that session_id gets new value after it is actually saved to the database, where it have to be unique. DB migration:
def change
create_table :sessions do |t|
t.string :session_id, :null => false
t.text :data
t.timestamps
end
add_index :sessions, :session_id, :unique => true
add_index :sessions, :updated_at
end
My question: How can I get the actual session.id value of a new session before the first response is rendered?
UPD:
I just created a new Rails app that uses ActiveRecord session store without Devise, and I can get session.id that is going to be set in cookie just before response with this code id application controller:
class ApplicationController < ActionController::Base
after_filter :show_session
def show_session
puts session.id
end
end
But in my existing app with Devise I get a value that really looks like a session id, but that doesn't match the value set in the cookie via Set-Cookie response header and the value actually saved to sessions table in database. Looks like Devise have a conflict with ActiveRecord session store in some way. Need to go deeper to figure it out.
UPD 2
Looks like I found the problem roots. As I said, I use Devise for authorization with Omniauth. According to the documentation, sign_in method resets session id for security reasons. But after that reset session.id returns the old value, that had been automatically set. I use this code as an Omniauth callback:
def facebook_access_token
sign_in @user
puts session.id
end
And in console I get session id different from the one set in the Set-Cookie response header. If I comment "sign_in" line, these values match. New question: how can I get the new session id value after it is been reset inside of sign_in method? Is it an internal Warden/Devise implementation or something?
Renewing is still important and you should not disable it
Also the new session id is generated after the execution of the controller, therefore after you have a chance to set the response to be sent to the client.
The solution is to manually trigger the renewing of the session id
In your ApplicationController
add the method:
protected
def commit_session_now!
return unless session.options[:renew]
object = session.options.instance_variable_get('@by')
env = session.options.instance_variable_get('@env')
session_id = object.send(:destroy_session, env, session.id || object.generate_sid, session.options)
session_data = session.to_hash.delete_if { |k,v| v.nil? }
object.send(:set_session, env, session_id, session_data, session.options)
session.options[:renew] = false
session.options[:id] = session_id
end
Then in your controller you just call this method before getting the session id for your response
def my_action
...
commit_session_now!
render json: {session_id: session.id}, status: :ok
end
The code in commit_session_now!
comes from Rack::Session::Abstract::ID#commit_session
https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L327
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With