-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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