Facebook token expiration and renewal, with Koala and omniauth-facebook
Asked Answered
J

3

22

I'm writing a Rails app that uses omniauth-facebook to authenticate the user against FB (and to get a FB OAuth access token for the user). The app then uses Koala to make various calls to the FB Graph API, using that saved OAuth token.

I update the saved token each time the user re-authenticates (typically when they log in to my app). Even so, that saved token will expire (or otherwise become invalid) from time to time.

What's the best practice around guarding against auth failures and updating the token while using Koala?

Should all calls be wrapped in begin/rescue blocks, with an exception handler that re-authenticates the user against FB?

Is there some way (using Koala) to take advantage of the 'extending access tokens' process described here? If not, are there best practices on writing my own code to extract the new token myself from a Koala call?

Josettejosey answered 20/4, 2012 at 16:14 Comment(0)
S
17

What I have is a before_filter that is triggered on every page that requires an active Facebook session. Something like this should work:

  before_filter :reconnect_with_facebook
  def reconnect_with_facebook
    if current_account && current_account.token_expired?(session[:fb]["expires"])

    # re-request a token from facebook. Assume that we got a new token so
    # update it anyhow...
    session[:return_to] = request.env["REQUEST_URI"] unless request.env["REQUEST_URI"] == facebook_request_path
    redirect_to(with_canvas(facebook_request_path)) and return false
  end
end

The token_expired? method looks like this:

def token_expired?(new_time = nil)
  expiry = (new_time.nil? ? token_expires_at : Time.at(new_time))
  return true if expiry < Time.now ## expired token, so we should quickly return
  token_expires_at = expiry
  save if changed?
  false # token not expired. :D
end
Sportswoman answered 1/5, 2012 at 1:3 Comment(4)
Thanks for the answer. Where/how is session[:fb]["expires"] getting set? You use it in your reconnect_with_facebook method above.Josettejosey
i set that the first time someone successfully signs in to create a session. All other connects are considered "reconnects"Sportswoman
Thanks. I implemented somewhat differently, but this was helpful in getting me on the right path.Josettejosey
What about handling situations when the token expires prematurely, e.g when the user changes their facebook pasword or removes the application?Helical
P
17

I came across this post which adapts code from the Railscast on Facebook to show how you can exchange the short-lived token for the 60-day one:

user.rb

 def self.from_omniauth(auth)

    # immediately get 60 day auth token
    oauth = Koala::Facebook::OAuth.new(ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_SECRET"])
    new_access_info = oauth.exchange_access_token_info auth.credentials.token

    new_access_token = new_access_info["access_token"]
    # Facebook updated expired attribute
    new_access_expires_at = DateTime.now + new_access_info["expires_in"].to_i.seconds

    where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
      user.provider = auth.provider
      user.uid = auth.uid
      user.name = auth.info.name
      user.image = auth.info.image
      user.email = auth.info.email
      user.oauth_token = new_access_token #originally auth.credentials.token
      user.oauth_expires_at = new_access_expires_at #originally Time.at(auth.credentials.expires_at)
      user.save!
    end
  end
Psychokinesis answered 23/5, 2013 at 18:54 Comment(1)
Thanks this is a nice method. Took me a while to figure out why my tests were complaining about a Koala::Facebook::OAuthTokenRequestError: type: OAuthException, code: 101, message: Missing client_id parameter. [HTTP 400]. Eventually, I realised that I hadn't defined ENV["FACEBOOK_APP_ID"] or ENV["FACEBOOK_SECRET"] for my test environment in secrets.yml.Eggers
F
0

You can do something like this where you check if the access_token is expired and generate another one.

 %w[facebook].each do |provider|
   scope provider, -> { where(provider: provider) }
 end

 def client
   send("#{provider}_client")
 end

 def expired?
   expires_at? && expires_at <= Time.zone.now
 end

 def access_token
   send("#{provider}_refresh_token!", super) if expired?
   super
 end

 def facebook_refresh_token!(token)
   new_token_info = 
   Koala::Facebook::OAuth.new.exchange_access_token_info(token)
   update(access_token: new_token_info["access_token"], expires_at: Time.zone.now + new_token_info["expires_in"])
 end

You can check gorails screencast that explains this on depth.

Frizzle answered 4/12, 2017 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.