Skip to content

Instantly share code, notes, and snippets.

@dmvt
Forked from herval/gist:951054
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmvt/9316144 to your computer and use it in GitHub Desktop.
Save dmvt/9316144 to your computer and use it in GitHub Desktop.
Ruby class which verifies Facebook authentication given the app secret and both a signed_request and user_id from the JavaScript SDK
# Personal Note: It was while working on this code that I heard about Jim Weirich's death. Sad day. RIP, Jim.
class FBAuth
attr_reader :errors, :signed_request, :user_id
def initialize(app_secret, signed_request, user_id)
@app_secret = app_secret
@errors = []
@now = Time.now
@signed_request = signed_request
@user_id = user_id
parse_data
end
def valid?
self.errors.size == 0 && token_verified? and user_ids_match? and not_expired?
end
private
def not_expired?
begin
# feel free to adjust the time threshold here
# I've found that 1 minute is too short. There seems to be upwards of a 90 second difference between
# Apple's time servers and Facebook's
if !@data or !@data['issued_at'] or (@now - 10.minutes) > Time.at(@data['issued_at'])
self.errors << 'The signed request has expired'
return false
else
return true
end
rescue
self.errors << 'Unable to evaluate the age of the signed request'
return false
end
end
def parse_data
begin
@token, @payload = self.signed_request.split(".")
# Facebook gives us a base64URL encoded string. Ruby only supports base64 out of the box, so we have to add padding to make it work
payload = @payload + '=' * (4 - @payload.length.modulo(4))
json = Base64.decode64(payload)
@data = JSON.parse(json)
# This is what your parsed JSON should look like
# @data
# => {
# "algorithm" => "HMAC-SHA256",
# "code" => "AQAt7dNd1mpSdSqbtVhqJSHItKrycfdqTXfKt9BPD9ClhoZun43zUyW_MyU0NhQsdJJlWS7nec1f1NBMZmXF0GGZ2ckjvB1RAsa-U9j2Z5NvQB0Gt1UoEj4GiyYpwXKWQXIQ5YzvDMKPLTOHwhR11BO2M14owSXjh7xhOmr7Lc0zFmcJSThgBEs4LH36ShaK6pNGkhZ6_K-YCHAQLR8tuGlqDYCtutzwgQIUzMV3oX00NtFekAFeDVXEjG1e4m0lQDk2GCZK4KCm2Do4v5OPrS3FQl6q0Ra2DnRmLuk3227VwZ_IYoAuKFqioUCfXw1lJQE",
# "issued_at" => 1393789041,
# "user_id" => "604441637"
# }
rescue
self.errors << 'Unable to parse signed request'
return false
end
end
def token_verified?
begin
parts = @data['algorithm'].split('-')
digestor = OpenSSL::Digest::Digest.new(parts[1])
digest = Base64.encode64("OpenSSL::#{parts[0]}".constantize.digest(digestor, @app_secret, @payload)).gsub('+', '-').gsub('=','').gsub('/', '_').chomp
if digest == @token
return true
else
self.errors << 'The digest did not match the token'
return false
end
rescue
self.errors << 'Could not verify the token specified by the signed request'
return false
end
end
def user_ids_match?
begin
if @data['user_id'] && @data['user_id'] == self.user_id
return true
else
self.errors << 'User id does not match the signed request'
return false
end
rescue
self.errors << 'Unable to evaluate user ids'
return false
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment