Créer une application facebook offline en utilisant l’api graph et le php-sdk

Mon but: Créer une application php pouvant pooler des informations sur un compte facebook sans que la personne en question ne soit forcement connectée à ce moment là.

Outils: Mon serveur web, l’api graph facebook et son php-sdk (http://github.com/facebook/php-sdk).

Initialement, je voulais faire un tutoriel sur l’intégration et l’utilisation de ce sdk avec symfony, mais cela aurait été un poil trop long et sans réel intérèt.

Avant toute chose, il ne faut pas oublier de créer une application facebook (facebook.com/developers), de noter son id, la clef api et la clef secrète, et de récupérer le php-sdk (hint: ‘git clone http://github.com/facebook/php-sdk.git’).

Vue la nature de l’application (type « connect », oauth et utilisation du sdk), quelques modifications sont à effectuer dans les settings:
Dans Connexion, spécifier l’URL connect (qui pointera vers l’endroit où vous allez mettre votre index.php), et activez « Canvas Session Parameter » et « OAuth 2.0 for Canvas (beta) » dans l’onglet Migrations.

La démo va comporter 2 parties: Une partie web (index.php), nécessaire pour enregistrer les utilisateurs dans l’application, les authentifier et récupérer le token d’accès, et une seconde, totalement « offline » qui permettra de récupérer les données dans facebook sans intervention tierce (fetch.php).

Le workflow d’enregistrement de l’utilisateur, de l’ajout des permissions et de la création de l’access token offline est le suivant:

⇀ L’utilisateur se pointe sur notre index.php, on lui affiche un lien pour se logger sur facebook ou pour ajouter l’application, qu’il doit cliquer;
⇀ En cliquant, il se retrouve sur facebook, se loggue si besoin, autorise l’application;
⇀ Il revient sur notre page, et nous lui demandons qu’il retourne sur facebook pour qu’il nous autorise à utiliser le mode offline et certaines autres permissions;
⇀ Facebook redirige l’utilisateur vers notre script, avec un paramètre « code »;
⇀ Nous effectuons une requète secrète avec le code donné et notre clef secrete vers facebook, pour récupérer le token illimité, que l’on sauvegarde précieusement.

Ces étapes peuvent être simplifiées (à base de Location: ), mais j’ai préféré bien distinguer les différentes étapes pour que ça soit clair (pour vous comme pour moi, plus tard).

Le premier script que je propose est plus simple que celui d’exemple proposé par le SDK de facebook. Il s’appelle donc index.php, sera le script par défaut dans mon répertoire web:

<?php

require_once 'php-sdk/src/facebook.php';

// Le script a besoin de connaitre un minimum d'information sur l'application facebook.
// On donne donc son ID et ses clefs d'utilisation:
$app_id = '... application id ...';
$api_key = '... la clef API ...';
$secret_key = '... la clef secrète ...';

// Construit une interface facebook avec les infos de notre application
// Noter qu'il est obligatoire d'avoir les extensions PHP json et curl.
$facebook = new Facebook(array(
                            'appId' => $app_id,
                            'secret' => $secret_key,
                            'cookie' => true
                        ));

// Recupere la session
// Si celle ci n'est pas valide, aucune interaction avec fb ne sera possible
$session = $facebook->getSession();

if($session)
{
    try {
        // On appelle l'API graph, ici graph.facebook.com/me
        // /me contient le nom de l'utilisateur mais aussi son ID facebook.
        $me = $facebook->api('/me');
    }
    catch(FacebookApiException $e) {
    }
}

// Si aucune session n'est detectee, alors on affiche un lien de login.
if( ! $session || ! $me )
{
    $loginUrl = $facebook->getLoginUrl();
    echo '<a href=' . $loginUrl . '>login</a>';
}
else
{
    $logoutUrl = $facebook->getLogoutUrl();
    // On pourrait dumper le résultat de l'appel à /me.
    echo 'Mon nom est ' . $me['name'] . '.';
    echo '<br />';
    echo '<a href=' . $logoutUrl . '>logout</a><br />';
}

Ensuite, pour effectuer des requêtes offline, il faut demander l’autorisation de l’utilisateur. Un appel à l’api Oauth 2.0 de facebook est ici nécessaire (voir http://developers.facebook.com/docs/api#authorization).

Pour ce faire, on rajoute donc une fonction « buildOAuthPermissionLink » et son lien prêt à être utilisé:

function buildOAuthPermissionLink()
{
    global $app_id;

    $my_site = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];

    $fb_link = 'https://graph.facebook.com/oauth/authorize?';
    $fb_link.= 'client_id=' . $app_id;
    $fb_link.= '&redirect_uri=' . $my_site;
    $fb_link.= '&scope=' . 'offline_access,read_stream';

    return $fb_link;
}
/* ... */
    echo 'Mon nom est ' . $me['name'] . '.';
    echo '<br />';
    echo 'Ask for permissions: <a href="' . buildOAuthPermissionLink() . '">click</a> ';
    echo '<br />';

Dès lors qu’on cliquera sur ce lien, on sera redirigé vers facebook qui, après avoir obtenu l’autorisation de l’utilisateur de partager les données, nous enverra un code qu’on pourra utiliser pour l’échanger contre un access_token spécifique de durée illimité. Cette fois-ci, c’est à nous de faire la requète:

if( isset($_REQUEST['code']) )
{
    $my_site = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];

    $fb_link = 'https://graph.facebook.com/oauth/access_token?';
    $fb_link.= 'client_id=' . $app_id;
    $fb_link.= '&redirect_uri=' . $my_site;
    $fb_link.= '&client_secret=' . $secret_key;
    $fb_link.= '&code=' . $_REQUEST['code'];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $fb_link);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, 0);

    $ans = curl_exec($ch);
    curl_close($ch);

    list($field, $value) = explode('=', $ans);
    if($field == 'access_token')
    {
        echo "Access token is '" . $value . "'<br />";
    }
}

Le token donné sera la clef de voute des appels effectués en mode offline. Il faut la conserver précieusement.
On va créer un 2nd script qui effectuera les appels en mode déconnecté. On l’appelle ‘fetch.php‘:

<?php

require_once 'php-sdk/src/facebook.php';

// De meme pour index.php, on a besoin des elements d'informations de l'applicaiton:
$app_id = '... l application id ...';
$api_key = '... la clef API ...';
$secret_key = '... la clef secrète ...';

// On cree l'objet:
$facebook = new Facebook(array(
                            'appId' => $app_id,
                            'secret' => $secret_key,
                            // Pas de cookie ... On est en mode offline, on donnera l'access token apres.
                        ));

// On fait un appel public, sans clef (ceci est l'url customisable)
$res = $facebook->api( '/patrick.marie' );
print_r($res);

// Ici le token qu'on a precieusement conservé suite à la fin de l'authentification OAuth
$access_token = '... l access token que l on a récupéré ...';

// On prepare un appel à /me. On a besoin de donner l'access token:
$params = array(
            'access_token' =>  $access_token
          );

$res = $facebook->api( '/me', 'GET', $params );
print_r($res);

// Dans l'index.php, j'ai demandé l'autorisation 'read_stream'. On va l'exploiter
// pour récupérer les derniers éléments de mon wall:
$wall = $facebook->api( '/me/feed', 'GET', $params );
print_r($wall);

// Ou ceux de mes amis:
$home = $facebook->api( '/me/home', 'GET', $params );
print_r($home);

Et voilà, cela clot cette première partie. Dans une seconde, je reviendrai sur les différents appels possibles au php-sdk, sur les POST possibles, les fonctions legacy et les requètes FQL.

  1. Bonjour Patrick,

    Tout d’abord, merci pour ce tutoriel !

    Quelle belle écriture, tout y est très bien expliqué, je suis fan :-)

    J’ai essayé de le mettre en application. La connexion s’effectue bien du côté de FaceBook.

    Le seul souci est que lorsque l’on revient sur la page initiale, l’utilisateur n’est toujours pas identifié. En faisant un var_dump de $session, cela le retourne NULL.

    Aurais-tu une idée là dessus ?

  2. As tu activé les bonnes options dans les settings de l’application ? Je pense notamment aux « Canvas Session Parameter » et « OAuth 2.0 for Canvas (beta) » (que j’utilise toujours) ?

  3. Oui, justement, j’ai suivit à la lettre et activé ces paramètres.
    Juste un détail j’ai includé jsonwrapper.php pour JSON PHP extension. Penses tu que cela puisse avoir une incidence ?

  4. Non, surement pas.
    Cependant, j’ai eu parfois beaucoup de soucis pour faire marcher tout ça et peut être que j’ai oublié un élément d’information.
    As tu néamoins var_dump($_REQUEST) pour regarder si il y avait le signed_session ? Pas mal de fois j’allais jusqu’à debug dans le php-sdk pour regarder ce qu’il faisait.

  5. Voila ce que j’ai exactement quand je fais un var_dum de $_REQUEST, mais visiblement, je n’ai pas de signed_session.

    ["code"]
    ["session"]
    « session_key » :
    « uid »:
    « secret »:
    « expires »:0
    « base_domain » :
    « access_token »:
    « sig »:

  6. Alors ça devrait être bon.
    Le $session = $facebook->getSession() devrait renvoyer quelque chose et tu devrais être capable de faire des appels à l’API.
    Je viens de vérifier en recréant une application avec le 1er exemple donné et cela passe impec’

  7. J’ai regardé du côté de facebook.php, lorsque le script charge la session depuis $_REQUEST tout va bien puis après avoir utilisé la fonction validateSessionObject() là, $session me retourne NULL.

  8. Si il va là dedans et renvoie NULL, c’est qu’il n’a pas pu vérifier la signature avec la secret key de l’application… Elle est correcte?

  9. j’ai vérifié visiblement, il n’y a pas de soucis.

  10. pourtant dans la session, j’ai bien toutes les infos et notamment le uid de l’utilisateur.

  11. ce qui est étrange, c’est que dans la fonction validateSessionObject($session), il ne considère pas $session comme an array mais comme un object stdClass et dans la première condition il y a is_array($session). Qu’en penses tu ?

  12. Je ne vois pas ce qui te fait dire qu’il considère $session comme un objet. Pour moi, c’est tout le temps comme un array. Si tu var_dump la $session, ça donne un stdObject? Ca devrait pas.

  13. Cela dit, ton jsonwrapper redéfinit-il json_decode ? Si oui, c’est finalement peut être lui qui pose problème, en te renvoyant un stdObject à la place de l’array() que sort le json_decode du php 5.3 (à vérifier, je dis ça de tête).

  14. Je confirme. Vu ce que facebook renvoie, jsonwrapper te sortira un stdObject à la place d’un array(). Du coup, le validateSessionObject() ne fonctionnera pas vu la condition qu’il impose d’avoir un array. C’est pour cela que tu ne dois pas avoir ta session.
    Il va faloir patcher le php-sdk (attention, json_decode n’est pas utilisé qu’à cet unique endroit) ou alors te passer du jsonwrapper (si possible…)

  15. Oui c’est bien ça, dès que je fais un echo de $session['access_token'] il me dis « Cannot use object of type stdClass as array ». De ton côté tu utilise aussi jsonwrapper dans ton exemple ?

  16. Non, je n’utilise aucune dépendance autre que le php-sdk car j’ai un PHP récent où il y a un maintenant un json_decode natif.
    Tu peux peut être essayer de caster le résultat de tes json_decode en array:

    $session = (array) json_decode(…);

    Ca devrait marcher.

  17. Super, ça cristallise! Du coup, j’ai dût caster les json_decode de l’ensemble du fichier, mais ça va il n’y en avait que 3. Merci pour ton aide!

    Je vais pourvoir continuer la mise en œuvre de ton tuto :) Bonne continuation et bonne soirée.

  18. Bonjour,
    J’ai une question à propos de l’ access_token ; J’ai vu dans la classe Facebook ceci :

    return $this->getAppId() .'|'. $this->getApiSecret();

    A priori nous pouvons donc construire notre access_token nous même. Pourquoi alors en générer un ? Le notre est il limité ?

  19. Un access_token est rattaché à un compte et fait la transition entre l’application et ce compte et les privilèges qui ont été accordés par l’utilisateur (pour prendre un exemple simple, récupérer sa liste d’ami). Sans ce token accordé par l’utilisateur, il semble trivial que facebook n’accordera pas l’accès à cette donnée (sinon il suffirait de créer une application pour avoir un accès total à toutes les données).
    Ce token, spécifique à l’application, permet juste de faire des requêtes sur facebook sur les données publiques qui ne nécessitent pas de permission particulière. Cela permet à facebook de controler les applications qui feraient des appels sans clef.

  20. Je me dois d’apporter une précision, je fais ma connexion et en même temps ma demande d’autorisation, puis dans $session, j’ai un expires à 0 et un access_token bien plus complexe que ce que j’ai pu préciser au dessus.
    Est-ce déjà l’access token que vous préciser de récupérer via CURL, et si oui, dois-je le stocker en BDD pour une utilisation ultérieur ?
    Sachant que je récupère ceci SEULEMENT lors de sa première connexion/acceptation des permissions.

  21. Sauf si je me trompe, vous devez utiliser les canvas si vous avez un access_token dans la session, et facebook envoie bien le bon. Dans le cas que je décris, je ne fais pas l’utilisation de canvas et j’utilise directement mon application web, et dans ce cas, lors de la rédaction je n’avais qu’une variable « code » obligatoire pour récupérer la clef. J’ai néanmoins du activer les options relatives aux canvas sans cela, ça ne fonctionnait pas. Peut-être que ça a changé depuis et qu’ils ne sont plus nécessaires (les précédents mois qui ont vu l’arrivée de la nouvelle api et de oauth ont été tumultueux).

  22. Il est vrai que je n’ai pas du tout de variable code ( à aucun moment ), j’ai uniquement la partie SESSION comme avait Michel plus haut, sans le code.

    Connaissez vous une méthode qui me permettrait de récupéré l’access_token dans le cas où je ne l’ai pas sauvegarder à la première connexion ?

    Merci pour votre aide.

  23. Il faut savoir que si l’utilisateur change son mot de passe de son compte facebook ou si il révoque une ou plusieurs permissions à l’application (ou s’il la retire du compte), il y a de grandes chances pour que l’access_token devienne invalide. Dans le cas des canvas, il devrait toujours y avoir une variable $session valide avec l’access_token. Dans le cas d’applications offline, pas le choix: invalider le compte en espérant que l’utilisateur revienne sur l’application pour lui redemander les permissions adéquates et obtenir un nouveau token.

  24. Donc actuellement, dans le cas ou je n’arrive pas à conserver l’access_token la première fois, je n’ai pas d’autre moyen que de le désinscrire de l’application pour obtenir à nouveau l’access_token ?

  25. Bonjour,
    J’ai un gros problème avec l’acesse token jarrive correctement a recupere la session dans la premiere page ou je suis avec tout uid acesse token session key etc ..
    Mais le probleme c’est que dans mon application j’ai des onglet qui me font changer de page puis si je change de page je narrive plus a recuperer une session le getsession ne marche plus ( il narrive ni a recupere une session dans l cookie ni dans le $_GET)
    Alors ma question est simple comment passer une session de page en page??

    Merci pour votre aide.

  26. C bon j’ai trouvée mon erreur il y a pas besoin de get la session a chaque des que j’ai l’acesse token tout roule il faut juste que je sauvegarde

    Merci quand meme pour le tuto

  27. Bonjour,
    J’essaye de mettre en oeuvre votre tuto.
    La connexion est bien réalisée, les demandes d’autorisations aussi. Après vérification, je constate que le compte facebook à bien enregistré l’autorisation de l’appli, en revanche, il ne me transfère pas l’access token (d’ailleurs il ne me donne pas non plus le nom de connexion à Facebook)
    Je récupère uniquement dans l’adresse du site un code=…
    Avez vous une idée du dysfonctionnement ?

  28. Bonjour, est ce que on peut récupérer l’id de la page qui contient l’application ?

  29. Bonjour,
    Pour info dans la version 3 du sdk, il faut remplacer $facebook->getSession() par $facebook->getUser().

Laisser un commentaire


NOTE - Vous pouvez utiliser les éléments et attributs HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>