arsduo/koala · error · Koala::Facebook::OAuthTokenRequestError

OAuthTokenRequestError.new(response.status, response.body)

Error message

OAuthTokenRequestError.new(response.status, response.body)

What it means

When a call to an /oauth endpoint (code redemption, app token, token exchange, client code) returns any 4xx status, fetch_token_string raises Koala::Facebook::OAuthTokenRequestError with http_status and response_body attached; APIError parsing fills in fb_error_code, fb_error_subcode, and fb_error_message from the Facebook JSON error. The meaning: Facebook understood the request and refused it (invalid or expired code, redirect_uri mismatch, wrong client credentials), so retrying the identical call will not help.

Source

Thrown at lib/koala/oauth.rb:312

              raise
            end
          end

          components.merge(token_info) if token_info
        else
          Koala::Utils.logger.warn("Signed cookie didn't contain Facebook OAuth code! Components: #{components}")
          nil
        end
      end

      def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
        response = Koala.make_request("/oauth/#{endpoint}", {
          :client_id => @app_id,
          :client_secret => @app_secret
        }.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options))

        raise ServerError.new(response.status, response.body) if response.status >= 500
        raise OAuthTokenRequestError.new(response.status, response.body) if response.status >= 400

        response.body
      end

      # base 64
      # directly from https://github.com/facebook/crypto-request-examples/raw/master/sample.rb
      def base64_url_decode(str)
        str += '=' * (4 - str.length.modulo(4))
        Base64.decode64(str.tr('-_', '+/'))
      end

      def server_url(type)
        url = "https://#{Koala.config.send(type)}"
        if version = Koala.config.api_version
          "#{url}/#{version}"
        else
          url
        end

View on GitHub (pinned to 47d052063e)

Solutions

  1. Redeem each code exactly once and persist the resulting token; make the callback idempotent (for example session[:token] ||= exchange).
  2. Keep the redirect_uri byte-identical between url_for_oauth_code and get_access_token; get_access_token_info defaults to @oauth_callback_url for both, so configure one source of truth.
  3. Inspect e.fb_error_message and e.fb_error_code: expired or already-used codes call for restarting the flow, parameter errors for fixing the request.
  4. On expiry, send the user back through url_for_oauth_code instead of retrying the dead code.

Example fix

// before
def callback
  session[:token] = @oauth.get_access_token(params[:code])
end

// after
def callback
  session[:token] ||= @oauth.get_access_token(params[:code])
rescue Koala::Facebook::OAuthTokenRequestError => e
  if e.fb_error_message.to_s.match?(/expired|invalid|been used/i)
    redirect_to @oauth.url_for_oauth_code(redirect_uri: callback_url)
  else
    raise
  end
end
Defensive patterns

Strategy: try-catch

Validate before calling

return if params[:code].blank? || session[:token] # a code is needed only once

Type guard

def fresh_code?(code)
  code.is_a?(String) && code.length > 20 && !session.key?(:token)
end

Try / catch

begin
  @oauth.get_access_token_info(code)
rescue Koala::Facebook::OAuthTokenRequestError => e
  if e.fb_error_message.to_s.match?(/expired|been used|invalid/i)
    restart_oauth_flow
  else
    raise
  end
end

Prevention

When it happens

Trigger: get_access_token(code) with a code that was already redeemed (codes are single-use; the get_user_info_from_cookies docs warn the method can be called once per session), a code older than roughly ten minutes, a redirect_uri at redemption that differs from the one used in url_for_oauth_code, or a client_id and client_secret that do not match the app that issued the code. parse_signed_cookie internally rescues this error for the Code was invalid or expired case and returns nil; other paths surface it directly.

Common situations: Double redemption: the server-side flow and the Facebook JS SDK both consuming the same code, or two requests racing on the same cookie; a dev redirect URI used in production; users sitting on the consent screen until the code expires; page reload re-posting the callback URL.

Related errors


AI-assisted analysis of arsduo/koala@47d052063e (2026-08-23). Data as JSON: /api/errors/809a34ac7ff230a4. Report an issue: GitHub.