Class: Facebook::SignedRequestVerifier
- Inherits:
-
Object
- Object
- Facebook::SignedRequestVerifier
- Defined in:
- app/services/facebook/signed_request_verifier.rb
Overview
Verifies and parses Meta's "signed_request" payload, used by both
Login (cookie-based flow) and the Data Deletion Callback webhook.
Meta's format is <base64url(signature)>.<base64url(json_payload)> where
the signature is HMAC-SHA256(encoded_payload, app_secret) — note the
HMAC is computed over the encoded payload string, not the decoded JSON.
Reference:
https://developers.facebook.com/docs/facebook-login/guides/advanced/oidc-token#signedrequest
https://developers.facebook.com/docs/development/create-an-app/app-dashboard/data-deletion-callback/
We can't reuse the parser bundled with omniauth-facebook
(OmniAuth::Facebook::SignedRequest.parse) directly because it raises on
failure — for a webhook controller we want a Result object we can branch
on without rescuing. The HMAC logic is otherwise identical.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- SUPPORTED_ALGORITHM =
'HMAC-SHA256'
Class Method Summary collapse
Class Method Details
.call(signed_request:, app_secret:) ⇒ Result
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'app/services/facebook/signed_request_verifier.rb', line 42 def self.call(signed_request:, app_secret:) return Result.new(valid?: false, payload: nil, error: 'missing_signed_request') if signed_request.blank? return Result.new(valid?: false, payload: nil, error: 'missing_app_secret') if app_secret.blank? encoded_sig, encoded_payload = signed_request.split('.', 2) return Result.new(valid?: false, payload: nil, error: 'malformed') if encoded_sig.blank? || encoded_payload.blank? payload = JSON.parse(base64_url_decode(encoded_payload)) unless payload['algorithm'] == SUPPORTED_ALGORITHM return Result.new(valid?: false, payload: nil, error: "unsupported_algorithm:#{payload['algorithm']}") end expected_sig = OpenSSL::HMAC.digest('SHA256', app_secret, encoded_payload) provided_sig = base64_url_decode(encoded_sig) unless ActiveSupport::SecurityUtils.secure_compare(expected_sig, provided_sig) return Result.new(valid?: false, payload: nil, error: 'bad_signature') end Result.new(valid?: true, payload: payload, error: nil) rescue JSON::ParserError => e Result.new(valid?: false, payload: nil, error: "json_parse:#{e.}") rescue ArgumentError => e Result.new(valid?: false, payload: nil, error: "base64_decode:#{e.}") end |