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

Invalid signature

Error message

Invalid signature

What it means

The last step of OAuth#parse_signed_request recomputes HMAC-SHA256 over the exact encoded envelope string using @app_secret and compares it to the decoded signature. A mismatch raises OAuthSignatureError with message Invalid signature: the payload was not signed by the secret this OAuth object holds. Tampering is one cause, but in practice the common cause is a credential mismatch: the signed_request (the fbsr_<app_id> cookie) was issued for a different app than the app_id and app_secret pair used to build the OAuth object.

Source

Thrown at lib/koala/oauth.rb:249

      # Parses a signed request string provided by Facebook to canvas apps or in a secure cookie.
      #
      # @param input the signed request from Facebook
      #
      # @raise OAuthSignatureError if the signature is incomplete, invalid, or using an unsupported algorithm
      #
      # @return a hash of the validated request information
      def parse_signed_request(input)
        encoded_sig, encoded_envelope = input.split('.', 2)
        raise OAuthSignatureError, 'Invalid (incomplete) signature data' unless encoded_sig && encoded_envelope

        signature = base64_url_decode(encoded_sig).unpack("H*").first
        envelope = JSON.parse(base64_url_decode(encoded_envelope))

        raise OAuthSignatureError, "Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'

        # now see if the signature is valid (digest, key, data)
        hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope)
        raise OAuthSignatureError, 'Invalid signature' if (signature != hmac)

        envelope
      end

      protected

      def get_token_from_server(args, post = false, options = {})
        # fetch the result from Facebook's servers
        response = fetch_token_string(args, post, "access_token", options)
        parse_access_token(response)
      end

      def parse_access_token(response_text)
        JSON.parse(response_text)
      rescue JSON::ParserError
        response_text.split("&").inject({}) do |hash, bit|
          key, value = bit.split("=")
          hash.merge!(key => value)

View on GitHub (pinned to 47d052063e)

Solutions

  1. Confirm the app the cookie belongs to (the cookie name embeds it as fbsr_<app_id>) matches the app_id passed to Koala::Facebook::OAuth.new, and that the secret is the current secret of that same app.
  2. Check how the secret is loaded: strip whitespace and quotes, and log its length (never its value) to confirm which ENV variable was used.
  3. If the secret was rotated in the dashboard, roll the new value out and redeploy.
  4. Rescue OAuthSignatureError, clear the stale fbsr_ cookie, and let the Facebook JS SDK write a fresh one at the next login.

Example fix

// before
@oauth = Koala::Facebook::OAuth.new(ENV["FB_APP_ID"], ENV["FB_APP_SECRET"])
auth = @oauth.get_user_info_from_cookies(cookies.to_h)

// after
@oauth = Koala::Facebook::OAuth.new(
  ENV.fetch("FB_APP_ID").strip,
  ENV.fetch("FB_APP_SECRET").strip,
  ENV.fetch("FB_CALLBACK_URL")
)
begin
  auth = @oauth.get_user_info_from_cookies(cookies.to_h)
rescue Koala::Facebook::OAuthSignatureError
  cookies.delete("fbsr_#{ENV.fetch("FB_APP_ID")}")
  auth = nil
end
Defensive patterns

Strategy: try-catch

Validate before calling

def facebook_oauth
  app_id = ENV.fetch("FB_APP_ID").strip
  secret = ENV.fetch("FB_APP_SECRET").strip
  raise ArgumentError, "Facebook credentials incomplete" if app_id.empty? || secret.empty?
  Koala::Facebook::OAuth.new(app_id, secret, ENV.fetch("FB_CALLBACK_URL"))
end

Try / catch

begin
  session[:fb_auth] = @oauth.get_user_info_from_cookies(cookies.to_h)
rescue Koala::Facebook::OAuthSignatureError
  cookies.delete("fbsr_#{APP_ID}") # stale or foreign cookie: force fresh login
  session[:fb_auth] = nil
end

Prevention

When it happens

Trigger: get_user_info_from_cookies or parse_signed_request with an fbsr_ cookie from app A while OAuth was constructed with the secret of app B; a secret with a typo, trailing whitespace, or pasted quotes; a secret rotated in the Facebook app dashboard but not yet in your config; a signed_request whose envelope was modified (even letter-case changes break the HMAC because it covers the exact encoded bytes).

Common situations: Dev and production credentials crossed through ENV; several Facebook apps in one codebase; a browser holding an fbsr_ cookie from an older app after an app-id migration; secrets loaded from YAML with stray characters.

Related errors


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