Was working on a Delphi server used to build a Facebook application, when I bumped into an odd Base64 decoding issue. If you look at the Facebook page documenting their Signed Request parameter, you can see it has a first part with an encoded signature (which I won't really mention here) plus some JSON data encoded in Base64. This looked trivial, but as I started using Indy's TIdDecoderMIME class, the result would be correct... but only at times. Other times, the final } of the JSON string was missing. Now, since there were three closing braces at the end of the specific error situation, I didn't spot the error immediately, the JSON string apparently looked good: If you look at it carefully, you'll see that the third final brace is missing. So now I could figure out while the JSON parser was crashing badly on me (by they way, I'm using JSON SuperObject for this specific project, nice class). Back to the Facebook documentation. They did say "a base64url encoded JSON object", with a link to Wikipedia, but the link doesn't really explain much: "modified Base64 for URL
variants exist, where the '
Saying there are some variants of an algorithm is not really the best way of documenting it. Facebook doesn't really say which "variant" it is implementing, but, well, at least you can guess among a few options. Moreover, the demo simply calls a PHP function, base_url_decode, and doesn't offer much advice. Initially I though of a problem with the Indy decoding, tried a couple of alternatives, went to nowhere. In fact Delphi has another SOAP-related Base64 decoded, who was doing the opposite, that is adding some garbage at the end of the string. Now that I knew where the issue was, it was easy to google "base64 decoding facebook" and find a nice blog post, http://qugstart.com/blog/ruby-and-rails/facebook-base64-url-decode-for-signed_request/. This is written in Ruby, but having a minimum of description, I could translate it over to Delphi in very little time (see image below for formatted version):
Update: as suggested in a comment below, this is not correct, as when there is one extra byte we need to add 3. However, in case of zero extra bytes, we shouldn't pad with 4, so evern the original Ruby code seems wrong. I've now fixed it with:
(4 - Length (strInput) mod 4) mod 4, as in the image below.
To be honest I have to say there is a Q&A in the Facebook page I didn't initially read. It says "if you are missing a few characters, you are probably using normal base64 decoding instead of base64url decoding. See
wikipedia
." But, again, wikipedia said there are several variants, come on Facebook! Even more surprising it that the provide a sample conversion at the very end of the page ("Do you have any example?)" , but that's actually only half of the what's encoded in the initial string! Thanks to some very approximate Facebook documentation I wasted a few hours. I think documentation for online APIs should be much more precise and do't take for granted everyone is using a single programming language in the world!
{"algorithm":"HMAC-SHA256","issued_at":1111111,"page":{"id":"-omitted-","liked":true,"admin":true},"user":{"country":"it","locale":"en_US","age":{"min":21}}
+
' and '
/
' characters of standard Base64 are respectively replaced by '
-
' and '
_
'," and "
Some variants allow or require omitting the padding '
=
' signs
"
strFixup := strInput + StringOfChar ('=', Length (strInput) mod 4); // nope, fixed below
strFixup := StringReplace (strFixup, '-', '+', [rfReplaceAll]);
strFixup := StringReplace (strFixup, '_', '/', [rfReplaceAll]);