Repository: tornadoweb/tornado

Issue:
Update auth.py
Response from Facebook Login doesn't contain the expires field. 

For example :
The response you will receive from this endpoint will be returned in JSON format and, if successful, is:

{
  "access_token": {access-token}, 
  "token_type": {type},
  "expires_in":  {seconds-til-expiration}
}

Would you be so kind to fix it.
Thanks in advance .


Discussion:
What is the consequence of this issue? (I don't use the facebook api myself) Does it mean that the fix of Tornado 4.4.3 is broken? Were things broken before or did they change from "expires" to "expires_in" at the same time they changed from form-encoded to json?

auth_test.py should be updated for this change.
Ive updated the auth_test.py file. 

Also You are right the fix of Tornado 4.4.3 is broken for the Facebook API. Because the Facebook changed the body of JSON as it was mention before :

{
"access_token": {access-token},
"token_type": {type},
"expires_in": {seconds-til-expiration}
}

Thanks.
hmm it is mentioned in https://github.com/tornadoweb/tornado/pull/1977#issuecomment-289509383 but was overlooked:

> The new format of this response is {"access_token": {TOKEN}, "token_type":{TYPE}, "expires_in":{TIME}}. We made this update to be compliant with section 5.1 of RFC 6749.

(which is an accurate quote from https://developers.facebook.com/docs/apps/changelog way down under "Changes from v2.2 to v2.3")
The change to `auth_test.py` doesn't actually test anything; something needs to look at `session_expires` and make sure it's being set appropriately.

Are there other changes here? It looks like `session_expires` was getting set to a string before the move to json, and is now an int. It might be best to convert the value back to a string when storing it in `session_expires` for backwards-compatibility. (or maybe it's fine - any user of the old string format would probably just cast it to an `int()` immediately, and that will still work if it's already an int). 

How important is this field in practice? It's not documented. Is this important enough to make a 4.4.4 release with this fix? 

Resolving patch:
diff --git a/tornado/auth.py b/tornado/auth.py
--- a/tornado/auth.py
+++ b/tornado/auth.py
@@ -954,6 +954,20 @@ def get(self):
         .. testoutput::
            :hide:
 
+        This method returns a dictionary which may contain the following fields:
+
+        * ``access_token``, a string which may be passed to `facebook_request`
+        * ``session_expires``, an integer encoded as a string representing
+          the time until the access token expires in seconds. This field should
+          be used like ``int(user['session_expires'])``; in a future version of
+          Tornado it will change from a string to an integer.
+        * ``id``, ``name``, ``first_name``, ``last_name``, ``locale``, ``picture``,
+          ``link``, plus any fields named in the ``extra_fields`` argument. These
+          fields are copied from the Facebook graph API `user object <https://developers.facebook.com/docs/graph-api/reference/user>`_
+
+        .. versionchanged:: 4.5
+           The ``session_expires`` field was updated to support changes made to the
+           Facebook API in March 2017.
         """
         http = self.get_auth_http_client()
         args = {
@@ -981,7 +995,7 @@ def _on_access_token(self, redirect_uri, client_id, client_secret,
         args = escape.json_decode(response.body)
         session = {
             "access_token": args.get("access_token"),
-            "expires": args.get("expires")
+            "expires_in": args.get("expires_in")
         }
 
         self.facebook_request(
@@ -1004,7 +1018,12 @@ def _on_get_user_info(self, future, session, fields, user):
         for field in fields:
             fieldmap[field] = user.get(field)
 
-        fieldmap.update({"access_token": session["access_token"], "session_expires": session.get("expires")})
+        # session_expires is converted to str for compatibility with
+        # older versions in which the server used url-encoding and
+        # this code simply returned the string verbatim.
+        # This should change in Tornado 5.0.
+        fieldmap.update({"access_token": session["access_token"],
+                         "session_expires": str(session.get("expires_in"))})
         future.set_result(fieldmap)
 
     @_auth_return_future


Test patch:
diff --git a/tornado/test/auth_test.py b/tornado/test/auth_test.py
--- a/tornado/test/auth_test.py
+++ b/tornado/test/auth_test.py
@@ -149,7 +149,7 @@ def get(self):
 
 class FacebookServerAccessTokenHandler(RequestHandler):
     def get(self):
-        self.write(dict(access_token="asdf"))
+        self.write(dict(access_token="asdf", expires_in=3600))
 
 
 class FacebookServerMeHandler(RequestHandler):
@@ -401,6 +401,9 @@ def test_facebook_login(self):
         self.assertTrue('/facebook/server/authorize?' in response.headers['Location'])
         response = self.fetch('/facebook/client/login?code=1234', follow_redirects=False)
         self.assertEqual(response.code, 200)
+        user = json_decode(response.body)
+        self.assertEqual(user['access_token'], 'asdf')
+        self.assertEqual(user['session_expires'], '3600')
 
     def base_twitter_redirect(self, url):
         # Same as test_oauth10a_redirect