title: Rails protect_from_forgery breaks login form if idle
body: <p>In our Rails application we use <code>protect_from_forgery</code> to prevent CSRF.</p>
  
  <p>However what we found is that if a user visits the login page and then goes off to make a cup of tea for the duration of what the app session expiration time is (let's say 15 minutes). The login just redirects back to the login page regardless of successful credentials or not (Depending on the environment, we get the error <code>InvalidAuthenticityToken</code>.) Trying to login again works fine. It only fails if the user has been on the page for longer than the session time.</p>
  
  <p>We thought this was weird because we haven't logged in yet... so what session is expiring? and surely a new session is being created on login even if one was created and had expired. Turns out (after reading this: <a href="https://nvisium.com/blog/2014/09/10/understanding-protectfromforgery/">https://nvisium.com/blog/2014/09/10/understanding-protectfromforgery/</a>) the CSRF protection in Rails actually uses a session to check the <code>authenticity_token</code> is valid. So basically the token expires when the session expires (depending on <code>session_store</code> setting), and you can't login without refreshing the page again.</p>
  
  <p>We solved this doing: <code>skip_before_action :verify_authenticity_token, only: [:create]</code> in our <code>SessionsController</code> but now that means our login form is no longer protected.</p>
  
  <p>What other options are there to fix this? Or is the solution we have used not as insecure as we think? Googling shows this line of code used lots of times, but surely it's a bad idea?</p>
  
  <p>Our other solution was to allow the Exception to happen, but handle it gracefully with:</p>
  
  <pre><code>rescue_from ActionController::InvalidAuthenticityToken do
    @exception = exception.message
    render 'errors/500', :status =&gt; 500, :layout =&gt; 'other'
  end
  </code></pre>
  
  <p>Though still hating the fact a user sitting on the login page for longer than the session timeout (in this case 15 mins) causes an error!</p>
  
  <hr>
  
  <p>Yet another solution we have come up with is to set the session_store to forever and then manually expire login sessions like this:</p>
  
  <pre><code>before_action :session_timeout, if: :current_user
  
  def session_timeout
    session[:last_seen_at] ||= Time.now
    if session[:last_seen_at] &lt; 15.minutes.ago
      reset_session
    else
      session[:last_seen_at] = Time.now
    end
  end
  </code></pre>