<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Rant first, think later. comments on Persisting a user session from RoR-&gt;PHP</title>
    <link>http://nicholaswright.org/blog/</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>Rant first, think later. comments</description>
    <item>
      <title>"Persisting a user session from RoR-&gt;PHP" by abica</title>
      <description>&lt;p&gt;Recently I was given the task of integrating a (complete for all intents and purposes) &lt;span class="caps"&gt;PHP&lt;/span&gt; application with our main Ruby on Rails application. Because the &lt;span class="caps"&gt;PHP&lt;/span&gt; application needed to display a similar interface and required knowledge of the user&amp;#8217;s account, I needed a way to access that data from the database both applications were now sharing. The only real requirement I had was that I absolutely didn&amp;#8217;t want to make the user login to the &lt;span class="caps"&gt;PHP&lt;/span&gt; app if they were already authenticated on the Rails side of things as this seemed unnecessary and interrupted the flow things.&lt;/p&gt;


	&lt;p&gt;I did a bit of searching and, while I did find the &lt;a href="http://wiki.rubyonrails.org/rails/pages/PhpSession"&gt;wiki page on going from &lt;span class="caps"&gt;PHP &lt;/span&gt;-&gt; Rails&lt;/a&gt;, I wasn&amp;#8217;t able to find anything that fit my specific need, so I set out to roll my own. I read an article this morning from somebody &lt;a href="http://work.rowanhick.com/2008/04/10/rails-php-sharing-the-same-session/"&gt;who had ostensibly encountered the same problem as I&lt;/a&gt;, and was able to come up with a much different solution than what I had come up with. Therefore I thought it would be fun to share some of the details of my approach.&lt;/p&gt;


	&lt;p&gt;The first way I thought of doing this was to post the session key across to the &lt;span class="caps"&gt;PHP&lt;/span&gt; app and then just do a lookup in the sessions table. This approach was unwieldy as it required me to expose the session key and it would also mean that users would be forced to enter the &lt;span class="caps"&gt;PHP&lt;/span&gt; portion from our provided links. Direct linking would simply not work. Thankfully I didn&amp;#8217;t have to worry about this since we&amp;#8217;re now using the fancy shmancy new &lt;a href="http://caboo.se/doc/classes/CGI/Session/CookieStore.html"&gt;CookieStore&lt;/a&gt; instead of the previously recommended &lt;a href="http://caboo.se/doc/classes/CGI/Session/ActiveRecordStore.html"&gt;ActiveRecordStore&lt;/a&gt;.&lt;/p&gt;


Now I just had to figure out how to read in the cookies. I took a quick look to see how &lt;a href="http://caboo.se/doc/classes/CGI/Session/CookieStore.html#M004043"&gt;Rails&lt;/a&gt; was &lt;a href="http://caboo.se/doc/classes/CGI/Session/CookieStore.html#M004042"&gt;storing&lt;/a&gt; the data in the cookie:
&lt;pre&gt;
     # File vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb, line 131
131:     def marshal(session)
132:       data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
133:       CGI.escape "#{data}--#{generate_digest(data)}" 
134:     end
&lt;/pre&gt;

&lt;pre&gt;
    # File vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb, line 124
124:   def generate_digest(data)
125:     key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
126:     OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
127:   end
&lt;/pre&gt;

	&lt;p&gt;Ok, so this meant all I had to was urldecode the cookie, base64 decode the cookie and hash the data so that I could compare the integrity hash passed through the cookie so I knew that I could trust the content. Since the cookies contained a hash from with the secret salt from our environment.rb, I had to make this manually make the &lt;span class="caps"&gt;PHP&lt;/span&gt; side aware of this. How you go about doing this is up to you, for this example let&amp;#8217;s assume I just copied the secret salt from our Rails environment and stuck it in a constant called &lt;span class="caps"&gt;SECRET&lt;/span&gt;_SALT. This was all pretty trivial from the php side of things:&lt;/p&gt;


&lt;pre&gt;
function parse_data_from_cookie( $cookie_name ) {
    $decoded_cookie = urldecode( $_COOKIE[ $cookie_name ] );

    // the cookie is passed through as data--digest, so split these out into their on variables
    list( $encoded_payload, $supplied_digest ) = explode( "--", $decoded_cookie );

    // construct a sha1 integrity hash with the data passed in the cookie and our secret salt
    $generated_digest = hash_hmac( 'sha1', $encoded_payload, SECRET_SALT );

    // can we trust that the data in this cookie came from our rails app and not from some malicious user?
    if ( $supplied_digest == $generated_digest ) {
        $data = base64_decode( $encoded_payload );
        return $data;
    // guess not
    } else {
        return false;
    }
}
&lt;/pre&gt;

	&lt;p&gt;This was working just as you would expect, however there was one caveat that you may have already anticipated. The data passed through in the cookie was actually marshaled Ruby. Because I really only cared about the user_id and none of the other cruft, the simpliest work around was to construct a custom cookie that only contained the user&amp;#8217;s id and that was not marshaled. I did this by making a simple method that would allow me to set all of the custom cookies I want, using the same digest generation and encryption process as the CookieStore was doing. I ended up with something a lot like this:&lt;/p&gt;


&lt;pre&gt;
def set_custom_cookie( key, value )
  data = ActiveSupport::Base64.encode64( value.to_s )
  digest = session.dbman.generate_digest( data )
  cookies[ key ] = CGI.escape( "#{ data }--#{ digest }" )
end
&lt;/pre&gt;

	&lt;p&gt;Note that I&amp;#8217;m using session.dbman to grab the CookieStore instance so that I can reuse the generate_digest method that handles all of the lightwork for me.&lt;/p&gt;


	&lt;p&gt;Now when a user logs in, I just set an additional cookie with that user&amp;#8217;s id in a custom cookie for the &lt;span class="caps"&gt;PHP&lt;/span&gt; side of things. When the user logs out I just delete this cookie. I actually manage the life of this cookie in a slightly different way, but that process is left as an for the reader. On the &lt;span class="caps"&gt;PHP&lt;/span&gt; side of things, if my cookie contains no data, if the digests don&amp;#8217;t match, or if I can&amp;#8217;t find the user in the database matching the passed user_id then I simply redirect them to our login page on the main app.&lt;/p&gt;


	&lt;p&gt;Enjoy.&lt;/p&gt;

</description>
      <pubDate>Thu, 10 Apr 2008 13:28:00 CDT</pubDate>
      <guid>&lt;a href="/blog/articles/2008/04/10/persisting-a-logged-from-ror-php"&gt;Persisting a user session from RoR-&gt;PHP&lt;/a&gt;</guid>
      <link>&lt;a href="/blog/articles/2008/04/10/persisting-a-logged-from-ror-php"&gt;Persisting a user session from RoR-&gt;PHP&lt;/a&gt;</link>
    </item>
  </channel>
</rss>
