Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails ActiveRecord store and new session

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?

like image 814
Bardt Avatar asked Sep 04 '13 09:09

Bardt


1 Answers

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

like image 98
Benj Avatar answered Nov 06 '22 07:11

Benj