How to get access_token in signed_request in canvas app, if an already authorized user needs more permissions now?

1.8k views Asked by At

Let's assume the following situation for a canvas app:

i) day 1: - Facebook app is created which needs read_stream,publish_stream,offline_access permissions. When a user comes to app for first time, authorize call redirects the user to a permission ALLOW / DENY screen , and when the user allows it redirects the user back to canvas url.

The canvas url has access_token in a signed request in its request parameters which can then be used to run the app.

No permission dialog is needed for same user coming to the app next time, as signed_request contains acess_token if the user had authorized the app in past.

The code looks like:

if(access_token received from signed request)
// do something with user information
else
// redirect user for authorization flow

ii) day 2: - Now, let's say I want to add one more permission to my list, user_birthday read_stream,publish_stream,offline_access,user_birthday` Now the following logic will have problems

  if(access_token received from signed request)
    // do something with user information  <-- the access_token does not have new permission
    else
    // redirect user for authorization flow

How can this additional permission addition be tackled efficiently, as API calls affect the performance of the app? I would not want to use something like :

https://graph.facebook.com/me/permissions?access_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Every time the application loads to check the permissions related to the token.

UPDATE:

Sharing a good method : Store the permission set along with the access_token with which it was received. eg. If current permissions are "basic_details-birthday-publish" (lets call it 1), store the access_token and permission set as

user  | access_token  | perm_set
Dhruv      sdfsdfsdf       1

Now,in your settings, whenever you need to ask for a new permission, create a new permission set "basic_details-birthday-publish-checkins" (lets call it 2),

then you need to show the permissions dialog only for users who have access token with perm_set = 1 and not for users who already have perm_set = 2, this will get rid of the need to check access_token of each user with "/me/permissions" api.

8

There are 8 answers

0
DhruvPathak On BEST ANSWER

An implementation suggestion.

Store the permission set along with the access_token with which it was received. eg. If current permissions are "basic_details-birthday-publish" (lets call it 1), store the access_token and permission set as

user  | access_token  | perm_set
Dhruv      sdfsdfsdf       1

Now,in your settings, whenever you need to ask for a new permission, create a new permission set "basic_details-birthday-publish-checkins" (lets call it 2),

then you need to show the permissions dialog only for users who have access token with perm_set = 1 and not for users who already have perm_set = 2, this will get rid of the need to check access_token of each user with "/me/permissions" api.

3
Björn Kaiser On

It would be a 2 step process:

  1. Check if the user granted you all required permissions by issuing a request to the Graph path "/me/permissions"

  2. If the user did not grant you all required permissions you need to go through the usual Allow/Deny process but this time with the new permission(s) added to the "scope" parameter.

Edit_: The only reliable way I know to verify permissions is by calling /me/permissions.

4
Soojoo On
$facebook = new Facebook(array(
                'appId' => 'xxxxxx',
                'secret' => 'xxxxx',
                'cookie' => true,
            ));
$code = @$_REQUEST["code"];//only get after log in into the app
if(empty($code)) 
{
$dialog_url     = "http://www.facebook.com/dialog/oauth?client_id=" 
                . $app_id . "&redirect_uri=" .  urlencode($canvas_page_url)."&scope=email,read_requests,offline_access,read_mailbox,user_relationships,user_likes,user_online_presence,user_activities,user_status,user_groups,manage_pages,friends_status,read_stream,friends_photos,publish_stream";
 echo("<script> top.location.href='" . $dialog_url . "'</script>");
}
 $token_url         = "https://graph.facebook.com/oauth/access_token?client_id="
                . $app_id . "&redirect_uri=" . urlencode($canvas_page) . "&client_secret="
                . $app_secret . "&code=" . $code;
$access_token   = @file_get_contents($token_url);

try with above code and reload the app ,then it will show a pop up to get access to extra permissions you add.

0
Kemal Dağ On

All answers here are based on the older facebook auth system, in which, when there is not a valid signed_request parameter, you redirect user to oauth url, with the scope parameter containing the permissions you require.

If no offline access permission is requested, then there is no problem since every user access token will be invalid in two hours, so after two hours of changing the scope parameter with new permissions, every new visit, will be redirected to oauth page with new scope, so facebook will handle it correctly.

Since you already took offline permissions and you need it, then accesstokens of users will not be easily invalitated(if only user changes password, or deactivates your app), the preceeding solution will not work correctly,

I accept that checking for permission errors in graph api calls, is a way to check wheather a user does not permitted you what you have asked for then redirect user to let him or her garant you your permissions. It is acceptable, but unneccessary

Because now, you can make your required permissions in the facebook app settings page, more specifically

go to https://developers.facebook.com/

and choose your app.

click settings -> Auth Dialog tab in the left menu. and choose your must permissions, that will allow all users are for sure, will come to your page with required permissions,

However, you cannot make any extended permission here mandotory.

So the only possible answer to your problem is, delete all your access tokens at once, as that will allow all returning users to be redirected to the new permission dialog.

But this solution will render you helples about getting information about your user base, to overcame this problem, you can only trigger this when a user visits your page, so after deleting his or her access tokeni user will probably revisit your page. But you have to keep an extra bit for every user, holding that wheather they are subjected to this access token deletion operation. If user is not subjected to this operation and visits your app, just delete the access token and redirect the user to oauth page with new permissions, if user already did it, then there is no problem for this kind of thing.

So my answer is the above alternatives. But the hardest and elegant way to this problem is, only ask for permissions whenever user interacts with your app to use that functionality of your app that requires the permission we talk about. That way, the oauth dialog CTR rate will go up since you will not make your user afraid of the length of the initial permissions that you ask for. Users will be more content about using your app. Whenever they need to do wonderfull things in your app, you can then ask kindly for a wonderfull permission.

good luck

0
Brent Baisley On

Don't forget you can also use the javascript SDK to prompt for permissions. The javascript SDK actually does everything inline. You can call the "login" function with the permissions parameters that you need, if they are already granted, nothing happens.

As others suggested, you can query the graph for /me/permissions, but use the javascript api method to get the information. So if you don't want to store the permissions the user granted, and subscribe to the real time update api to make sure they stay up to date, you can use the javascript api to do everything inline, from the client side.

You're pretty much eliminating your server from playing any role and linking the user directly to Facebook via javascript. Facebook actually does some caching on the client side so the calls may be instantaneous.

Here is a Facebook blog post about prompting for missing permissions. https://developers.facebook.com/blog/post/576/

0
Jeff Sherlock On

First, I'll state the obvious. You should use the /me/permissions endpoint. It's the only way to know for sure if the access token is valid and has all of the permissions you need/want. Since you said you want a solution that doesn't hit this endpoint every time the app is loaded, I'll move on.

The only way I can think of to not check the /me/permissions API call is to keep track of the permissions on your own server with a simple table mapping user_id to the permissions for the user. When a new user is authorized, you add a row to your database table for that fb user id and the list of permissions they've authorized. Now when they come back, you can get the signed_request and lookup in your table whether they have all the permissions you want. If they don't, you prompt them to authorize the additional permissions and update your table if they grant you those permissions.

Since you're already asking for offline_access, I assume you're storing the access tokens somewhere anyway, so adding another table for a list of permissions on the access token seems like it wouldn't be too much of an additional burden.

There are some obvious pitfalls with this design (inconsistency with FB), but if your primary objective is to avoid the /me/permissions endpoint, then this should work for you.

0
DMCS On

You have three choices and one of them is the one you already said you don't want to do.

  1. Check me/permissions and loop thru to see if all of them are still there.

  2. try/catch every API call and watch for the error received (see http://fbdevwiki.com/wiki/Error_codes) to see if it is #10 API_EC_PERMISSION_DENIED. If so, then ask the user for permissions again.

  3. Write your app so it is backwards compatible with the old permission set, so only the new functionality in it appears for the user's who've granted the newer permissions. Of course you will need to try/catch every API call to find out what parts of your app you need to hide/show.
1
ifaour On

Well, the most efficient solution would require what @Jeff suggested plus using the Real-time API.
STEP 1: Create a permissions table to store the user permissions when a user "connects" to your app the first time.
STEP 2: subscribe to the permissions object, example:

<?php
require '../src/facebook.php';

$facebook = new Facebook(array(
  'appId'  => 'APP_ID',
  'secret' => 'APP_SECRET',
));

$app = get_app_access_token("APP_ID", "APP_SECRET");
parse_str($app);

$realtime_params = array(
    'object'=>'permissions',
    'fields'=>'read_stream,publish_stream', // most recent permissions required by your app
    'callback_url'=>'CALLBACK_URL_HERE',
    'verify_token'=>'STRING_THAT_SHOULD_BE_PRESENT_IN_THE_CALLBACK_PAGE_TOO',
    'access_token'=>$access_token
);

try {
$res = $facebook->api("/APP_ID/subscriptions", "post", $realtime_params);
} catch (FacebookApiException $e) {
echo '<pre>'.htmlspecialchars(print_r($e, true)).'</pre>';
}
function get_app_access_token($id,$secret) { 
    $token_url =    "https://graph.facebook.com/oauth/access_token?" .
                    "client_id=" . $id .
                    "&client_secret=" . $secret .
                    "&grant_type=client_credentials";
    return file_get_contents($token_url);
}

More about this in the Real-time Updates documentation.

STEP 3: Your callback page should handle the post request sent by Facebook, if a user revoked one of the permissions (for example removed the publish_stream permission); in that case, Facebook will send you something like (after decoding the request, see here):

Array
(
    [object] => permissions
    [entry] => Array
        (
            [0] => Array
                (
                    [uid] => 100003355152933
                    [id] => 100003355152933
                    [time] => 1327005647
                    [changed_fields] => Array
                        (
                            [0] => publish_stream
                        )

                )

        )

)

Regardless of what have been changed, I would use the above request as a trigger, query /user_id/permissions connection and update the permissions table.

Now you have two cases:

if(access_token received from signed request)
    if(permissions from table are full)
        // do something with user information
    else
        // ask for missing permission and update permissions table
else
    // redirect user for authorization flow
    // upon full authorization, save to the permissions table too

Obviously what have been said in other answers should be used too. You should always use "try...catch" clauses and check for permission related errors and act upon that!