I. Introduction▲
Hier, j'ai finalement eu le temps de finir et de rafistoler Zend_Oauth, l'implémentation du consommateur d'OAuth Core 1.0 Revision A specification. Après l'avoir terminé, je l'ai utilisé pour écrire rapidement une interface assez simple pour publier quelques tweets sur Twitter pour mes tests. Avec un peu de documentation et quelques tests unitaires en extra, l'implémentation consommateur devra trouver sa place dans la version 1.10(1) de Zend Framework… ainsi que l'implémentation du serveur, enfin je crois.
Dans cet article je vais vous expliquer comment écrire rapidement un client Twitter que vous pourrez utiliser pour publier des tweets (ces petits messages de moins de 140 caractères) une fois que vous êtes autorisé via le protocole OAuth.
Vous pouvez télécharger tous les fichiers nécessaires (assurez-vous de les modifier tel qu'il est décrit) ou les extraire à partir de git à l'adresse suivante:http://github.com/padraic/Tweet-Lite/tree/master
II. Qu'est-ce que OAuth? ▲
Si vous ne savez pas ce qu'est OAuth, la spécification d'OAuth le décrit ainsi :
Le protocole OAuth permet à des sites Web ou à des applications (consommateur) d'accéder à des ressources protégées d'un web service (fournisseur de service) via une API, sans contraindre les utilisateurs à divulguer leurs informations d'identification du fournisseur de services aux consommateurs. En général, OAuth crée une méthodologie librement implémentable et générique pour l'API d'authentification.
En d'autres termes, c'est un moyen de permettre à des sites Web d'accéder à vos données sur d'autres services via un service API, de même que l'API de Twitter ou Google Gdata, sans fournir à ces sites Web votre nom d'utilisateur et mot de passe. Au lieu de cela, OAuth vous permet d'autoriser de tels sites à accéder à vos données sans qu'ils aient besoin de votre nom d'utilisateur ou mot de passe, ils utilisent seulement un jeton (token) d'accès fourni par votre fournisseur de service, ainsi vous pouvez à tout moment leur interdire l'accès si vous le désirez. L'avantage est immédiatement évident votre nom d'utilisateur et mot de passe ne sont pas partagés ou distribués à des sites potentiellement indignes de confiance. La surabondance de services utilisant Twitter sont un excellent exemple, jusqu'à récemment ils réclament tous votre nom d'utilisateur et mot de passe Twitter et honnêtement, comment pouvez-vous savoir qu'il ne s'agit pas d'un abus ? Puisqu'ils le disent ? OAuth élimine ce problème.
Le protocole fonctionne comme suit : le site web (consommateur) voulant avoir accès à vos données via un fournisseur de services, contacte celui-ci en utilisant une requête http pour récupérer un jeton non autorisé. Le client vous redirige ensuite vers votre fournisseur de service pour que vous puissiez donner l'accès au client. URL de redirection va contenir le jeton non autorisé comme paramètre. Si vous approuvez la demande, vous serez redirigé vers le site qui est à l'origine de la demande avec un code de vérification lié a URL. Maintenant le site sait que vous lui avez accordé l'accès, alors il contacte votre fournisseur de services en incluant le jeton fraîchement approuvé (une fois de plus) et le code de vérification dans l'URL. La réponse devrait être un jeton garantissant un accès complet (associé à l'utilisateur) que le client pourra utiliser dans les futures demandes d'accès a vos données (jusqu'à son expiration ou que vous lui retiriez son autorisation). À présent, vous pouvez vous débarrasser du jeton de la demande - dans le jargon d'OAuth vous avez échangé un jeton de demande d'accès non autorisé contre un jeton d'accès autorisé.
III. La préparation est toujours inévitable !▲
Avec cette compréhension entre les mains, passons à la réalisation de ce petit client Twitter comme exemple ! Vous aurez besoin de télécharger Zend Framework (la dernière versionla dernière version ou par Subversion). Vous aurez besoin aussi de récupérer Zend_OAuth depuis l'incubateur de Zend Framework vu qu'il ne fait pas partie du dépôt principal. Une fois que vous avez tout enregistré quelque part, notez bien les chemins de ces librairies pour que vous puissiez les inclure par la suite dans votre PHP include_path.
Du côté de Twitter nous avons trois étapes :
Premièrement, obtenez un compte Twitter ! J'espère que vous en avez déjà un le suivant est le mien @padraicb.
Deuxièmement, vous avez besoin de configurer votre système afin de définir un nouveau domaine local. Sous Linux vous y parviendrez en éditant /etc/hosts, vous aurez besoin des droits du superutilisateur root, vous pouvez utiliser la commande sudo comme ceci " sudo myeditor /etc/hosts ". Sous Windows le fichier équivalent peut être trouvé dans le dossier C:\Windows\System32\drivers\etc\hosts, vous aurez besoin des droits d'Administrateur pour pouvoir l'éditer. Ajouter un domaine local est assez simple, il est nécessaire pour pouvoir utiliser l'API de Twitter en local, Twitter refuse toutes requêtes venant depuis localhost ou 127.0.0.1, mais il ne filtre pas un nom de domaine local vu que c'est infaisable et ça mettrait en rogne la plupart des développeurs. Modifiez le fichier pour inclure une nouvelle entrée comme suit :
127
.0
.0
.1
mytwitterclient.tld
Une fois enregistré, votre navigateur doit immédiatement vous rediriger vers localhost/127.0.0.1 à n'importe quelle adresse sous la forme d'http://mytwitterclient.tld. Si votre serveur web est démarré et que vous avez déjà un site à la racine de votre dossier WEB, vous y serez redirigé. Si vous trouvez ça gênant et que vous ne pouvez pas jouir d'une telle simplicité, vous pouvez jouer sur la configuration du Virtualhosting pour définir un chemin différent.
Troisièmement, le Fournisseur OAuth de Twitter ne donne pas un jeton d'accès pour chaque Tom, Dick et Harry, enfin si, mais il aimerait bien savoir au préalable que vous allez vous appeler Tom, Dick ou Harry, a cet effet vous avez besoin d'enregistrer de toutes les applications pour récupérer la clef d'accès à son service OAuth. C'est à peu près la même chose pour les fournisseurs de services similaires. Visitez l'adresse http://twitter.com/oauth_clients et connectez-vous en utilisant votre compte Twitter, vous serez amené sur une page où vous pourrez enregistrer de nouvelles applications/consommateur ou en supprimer. Enregistrez une nouvelle application que vous pouvez nommer comme bon vous semble, mais assurez-vous de :
- Choisir navigateur comme type d'application
- Définir Callback URL a http://mytwitterclient.tld/callback.php (modifier le domaine et le chemin de vos préférences)
- Sélectionner l'accès en mode lecture/écriture vu le fait qu'on a besoin d'envoyer de nouveaux tweets donc d'un accès en écriture.
Nous n'allons pas utiliser twitter pour se connecter. Immédiatement après l'enregistrement vous serez redirigé sur une page qui contient les différents points de terminaison OAuth et le plus important, la clef et code secret du consommateur que vous utiliserez dans votre application. Soyez sans crainte vous pouvez revisiter cette page à tout moment.
IV. Lumière sur le code source effectif▲
Jusqu'à présent nous avons tenu à un concept de simplicité, ici, ça va être un effort de codage, mais pas un exercice d'écriture de la prochaine génération de super application propulsée par Zend Framework. Tout jeton d'accès permanent (celui-ci peut être indéfiniment rafraîchi alors ne craignez pas de le perdre) sera enregistré dans la session courante en vue de sa réutilisation donc on peut se dispenser d'une base de données.
<?php
define('URL_ROOT'
,
'http://mytwitterclient.tld'
);
$configuration
=
array
(
'callbackUrl'
=>
'http://mytwitterclient.tld/callback.php'
,
'siteUrl'
=>
'http://twitter.com/oauth'
,
'consumerKey'
=>
'xxxxxxxxxxxxxxxxxxxxxxx'
,
'consumerSecret'
=>
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
);
L'idéal, c'est tout ce que vous devez ajouter à votre configuration. Il faut garder à l'esprit que les fournisseurs de services peuvent définir leurs propres besoins, les points de terminaison des services (endpoints) et même la méthode de requête préférée. Une configuration complète ressemblerait de près à ceci (reste valide pour Twitter, d'autant plus qu'elle démontre la configuration par défaut utilisée en interne par Zend_Oauth).
<?php
define('URL_ROOT'
,
'http://mytwitterclient.tld'
);
$configuration
=
array
(
'version'
=>
'1.0'
,
// there is no other version...
'requestScheme'
=>
Zend_Oauth::
REQUEST_SCHEME_HEADER,
'signatureMethod'
=>
'HMAC-SHA1'
,
'callbackUrl'
=>
'http://mytwitterclient.tld/callback.php'
,
'requestTokenUrl'
=>
'http://twitter.com/oauth/request_token'
,
'authorizeUrl'
=>
'http://twitter.com/oauth/authorize'
,
'accessTokenUrl'
=>
'http://twitter.com/oauth/access_token'
,
'consumerKey'
=>
'xxxxxxxxxxxxxxxxxxxxx'
,
'consumerSecret'
=>
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
);
Nous n'allons pas nous attacher aux détails pour le moment, les deux points à retenir sont "requestScheme" et "signatureMethod". Le schéma de requête est la manière d'envoyer les paramètres du protocole OAuth au fournisseur de service. Par défaut (c'est l'option préférée par la spécification). Nous envoyons ces paramètres par le biais d'un en-tête d'autorisation. Nous avons deux autres méthodes comme alternatives, telles que données brutes via un POST ou telles que partie de la chaîne de requête (POST ou GET). La méthode par défaut est soutenue comme étant recommandé par la spécification OAuth. Dans Zend_Oauth, la méthode de signature a quatre options admissibles HMAC-SHA1, HMAC-SHA256, RSA-SHA1 et PLAINTEXT. À défaut HMAC-SHA1 est le plus judicieux, mais les autres options peuvent être requises par certains fournisseurs de service. Finalement, vous allez remarquer qu'il y a trois URL et pas de " SiteUrl " comme c'est le cas dans la version courte. La définition de l'option SiteUrl est utile dans le cas ou les points de terminaison effectifs utilisent la convention /request_token, /authorize et /access_token comme chemins dans leurs points de terminaison.
Maintenant, regardons common.php (prévisiblement le fichier et tous les autres à l'exception de config.php vont l'inclure)
<?php
/**
* Si vous n'avez pas modifié le fichier php.ini pour ajouter le Zend Framework et le
* Zend Framework Incubateur dans l'include_path de PHP, alors faites-le ici.
* N'utilisez pas ma configuration !
*/
set_include_path(
'/home/padraic/projects/zf/trunk/library'
.
PATH_SEPARATOR .
'/home/padraic/projects/zf/incubator/library'
.
PATH_SEPARATOR .
get_include_path()
);
//Assurez-vous que le consommateur Zend_Oauth est chargé
require_once 'Zend/Oauth/Consumer.php'
;
// Démarrer la session
session_start();
//Inclure les données de configuration de notre client OAuth (tableau $configuration)
include_once './config.php'
;
// Créer une nouvelle instance du consomateur pour utilisation
$consumer
=
new
Zend_Oauth_Consumer($configuration
);
Le fichier common.php est assez simple. Vous allez remarquer que nous avons défini ici notre include_path (comme vous pouvez le faire aussi dans le php.ini), démarrer notre session, créer une nouvelle instance d'un consommateur OAuth. Actuellement Zend_Oauth offre une implémentation consommateur complète, l'implémentation serveur suivra dans un futur très proche.
Il est temps de jeter un œil au point de départ de notre petite application, index.php
<?php
// Inclure le code commun
include_once './common.php'
;
// Avons nous déjà un jeton d'accès valide ou doit en demander un ?
if
(!
isset($_SESSION
[
'TWITTER_ACCESS_TOKEN'
]
)) {
// Suposant qu'on a besoin d'aller en chercher un !
$token
=
$consumer
->
getRequestToken();
$_SESSION
[
'TWITTER_REQUEST_TOKEN'
]
=
serialize($token
);
/**
* Maintenant, redirigeons l'utilisateur vers Twitter pour qu'il puisse se connecter et
* approuver notre accès
*/
$consumer
->
redirect();
}
/**
* Passez ceci s'il y a blocage! Vous devez avoir un jeton d'accès. Créons un
* simple formulaire pour que l'utilisateur puisse envoyer ses tweets
*
* on fait un echo sur la décalation de xml dans la cas ou les short tags sont activés - grrr.
* Ils sont une nuisance parfois sanglante.
*/
echo '<?xml version="1.0" encoding="UTF-8"?>'
;
?>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
xml:lang
=
"
en
"
>
<
head>
<
title>
Tweet Lite Script<
/title
>
<script language
=
"
javascript
"
type
=
"
text/javascript
"
>
<!--
function
imposemax
(
Object
)
{
return
(
Object
.
value.
length <=
140
);
}
-->
</
script>
<
/head
>
<
body>
<?php
if
(isset($_GET
[
'result'
]
) &&
$_GET
[
'result'
]
==
'true'
):
?>
<
p style
=
"
background-color: lightgreen;
"
>
You successfully sent your tweet!<
/p
>
<?php
elseif
(isset($_GET
[
'result'
]
) &&
!
empty($_GET
[
'result'
]
)):
?>
<
p style
=
"
background-color: red;
"
>
Oops! Tweet wasn't accepted by Twitter. Probable failure:<
/p
>
<
div style
=
"
background-color: red;
"
>
<?php
echo $_GET
[
'result'
];
?>
<
/div
>
<?php
endif
;
?>
<
p>
All that work on Zend_Oauth, and all you do is send Tweets with it?
<
img src
=
"
/templates/default/img/emoticons/wink.png
"
alt
=
"
;-)
"
style
=
"
display: inline; vertical-align: bottom;
"
class
=
"
emoticon
"
/
>
<
br/
><br/
></p
><form action
=
"
tweet.php
"
method
=
"
post
"
id
=
"
statusform
"
>
<
p>
What do you want to say to Twitterland using 140 characters?<
/p
>
<!-- Bit of a JS hack without dumping in more code to do it right,
to impose 140 char limit. It'll prevent deleting when limit reached -->
<
textarea name
=
"
status
"
id
=
"
status
"
rows
=
"
2
"
cols
=
"
70%
"
onkeypress
=
"
return imposemax(this);
"
></textarea
>
<
br/
><input type
=
"
submit
"
id
=
"
submit
"
value
=
"
Tweet!
"
/
>
<
/form
>
<
p><br/
><br/
>
Click below to delete the Access Token and force start another authorisation leg:<
br/
>
<
a href
=
"
clear.php
"
>
Clear Access Token<
/a
></p
>
<
/body
>
<
/html
>
Dans la première partie de notre script, nous vérifions si nous n'avons pas enregistré préalablement un jeton d'accès (nécessaire pour utiliser l'API de Twitter pour l'utilisateur en cours) comme une variable de session sérialisé. S'il existe nous allons afficher notre formulaire, accepter le champ texte statut et l'envoyer à tweet.php. Si nous n'avons pas un jeton d'accès, nous sommes contraint d'en obtenir un. L'appel "$consumer->getRequestToken(); ", demande à Zend_Oauth_Consumer d'exécuter la requête d'un nouveau jeton de demande non autorisé. En retour nous en recevrons un avec lequel nous pouvons rediriger l'utilisateur vers Twitter (URL de redirection doit inclure le jeton) pour qu'il approuve la requête. Avant de rediriger l'utilisateur nous allons sérialiser le jeton et l'enregistrer dans une variable de session. Gardez à l'esprit que tout jeton dans Zend_Oauth est de type Zend_Oauth_Token.
Ici, l'utilisateur a la possibilité d'accepter ou de rejeter notre demande d'accès comme c'est indiqué par Twitter, à tout moment l'utilisateur peut retirer son autorisation, lorsque l'utilisateur clique sur le grand bouton "Allow " Twitter va le rediriger vers notre application. Le code de vérification sera inclus avec l'URI de redirection (c'est comme un code PIN) avec lequel on pourra faire la demande d'un jeton d'accès autorisé. Maintenant que nous avons tout configuré et comme il a été ajouté lors de l'enregistrement, l'utilisateur devra être redirigé vers callback.php
<?php
// Inclure le code commun
include_once './common.php'
;
/**
* Si quelqu'un frappe a la porte en utilisant l'URL de rappel (callback) -
* si elle contient des données GET , il se peut que quelqu'un vient d'approuver
* l'accès à son compte OAuth. Ainsi le mieux est d'échanger notre jeton de demande
* pour un jeton d'accès autorisé. Le jeton de demande est imminent pour fait l'échange, n'est ce pas ?
*/
if
(!
empty($_GET
) &&
isset($_SESSION
[
'TWITTER_REQUEST_TOKEN'
]
)) {
$token
=
$consumer
->
getAccessToken($_GET
,
unserialize($_SESSION
[
'TWITTER_REQUEST_TOKEN'
]
));
$_SESSION
[
'TWITTER_ACCESS_TOKEN'
]
=
serialize($token
);
/**
* Maintenant que nous avons un jeton d'accès, on peut se débarrasser du jeton de demande
* En production garder un œil sur la collecte des RTS qui ne sont jamais utilisées.
*/
$_SESSION
[
'TWITTER_REQUEST_TOKEN'
]
=
null
;
// Avec un jeton d'accès en main, essayons encore d'accéder au client
header('Location: '
.
URL_ROOT .
'/index.php'
);
}
else
{
// Fausse demande ? Quelqu'un de malveillant essaye de faire quelque chose.
exit('Invalid callback request. Oops. Sorry.'
);
}
Dans le fichier callback, nous vérifions la demande entrante à la recherche d'une chaîne de requête et si nous avons un jeton de demande en suspens, nous passons le tableau $_GET et le jeton de demande (désérialisé depuis la session) à la méthode Zend_Oauth_Consumer::getAccessToken(). Ceci va faire une autre requête à Twitter qui va inclure le jeton de demande avec et le code de vérification (PIN) issu de la redirection de l'utilisateur. Twitter devrait répondre avec un jeton d'accès autorisé, que nous allons sérialiser et enregistrer dans une variable de session pour une utilisation ultérieure (dans un cadre d'une réelle application le jeton devrait être associé au compte de l'utilisateur pour un usage permanent), avec ce jeton en main, nous pouvons détruire le jeton de demande qui ne nous est à présent d'aucune utilité. À cet effet, on peut rediriger l'utilisateur vers index.php où nous serons accueillis par notre application dans toute sa splendeur (ou non).
Une fois que nous sommes ici, notre autorisation Oauth a été faite, mais, attendez ce n'est pas encore fini !
Les utilisateurs peuvent à présent entrer un message de leur statut de moins de 140 caractères et l'envoyer à Twitter. Le formulaire est envoyé à tweet.php, voyons ce qu'il fait :
<?php
// Inclure le code commun
include_once './common.php'
;
// Vérifion si nous avons un message dans notre POST à envoyer vers Twitter
if
(!
empty($_POST
) &&
isset($_POST
[
'status'
]
)
&&
isset($_SESSION
[
'TWITTER_ACCESS_TOKEN'
]
)) {
/**
* La manière la plus simple d'utiliser OAuth vu que nous avons un jeton
* d'accès est d'utiliser une instance préconfigurée de Zend_Http_Client
* qui va signer et encoder toutes nos demandes sans tâches supplémentaires.
*/
$token
=
unserialize($_SESSION
[
'TWITTER_ACCESS_TOKEN'
]
);
$client
=
$token
->
getHttpClient($configuration
);
$client
->
setUri('http://twitter.com/statuses/update.json'
);
$client
->
setMethod(Zend_Http_Client::
POST);
$client
->
setParameterPost('status'
,
$_POST
[
'status'
]
);
$response
=
$client
->
request();
/**
* Vérifions si la réponse JSON se réfère aux détails de notre tweet
* (en supposant qu'il a bien été posté). Les gourous API peuvent me
* corriger après.
*/
$data
=
json_decode($response
->
getBody());
$result
=
$response
->
getBody();
// report in error body (if any)
if
(isset($data
->
text)) {
$result
=
'true'
;
// response includes the status text if a success
}
// TTweet envoyés (espérons-le), de redirection a l'accueil...
header('Location: '
.
URL_ROOT .
'?result='
.
$result
);
}
else
{
// Fausse demande ? Quelqu'un de malveillant essaye de faire quelque chose.
exit('Invalid tweet request. Oops. Sorry.'
);
}
Quand j'ai dit que ce n'est pas fini, c'est dans le sens qu'à partir de maintenant, chaque requête API concernant notre utilisateur doit être autorisée avec le jeton d'accès Oauth. Ceci se fera facilement en signant toutes les requêtes avec un hachage HMAC-SHA1. Il est très pénible de faire ça manuellement, donc pour l'utilisation d'un objet jeton d'accès, récupérer simplement une instance de Zend_Http_Client (hérité par Zend_Oauth_Client) qui va englober tous les paramètres du protocole OAuth et la demande de signature d'une manière transparente. Utilisez-le au même titre que vous utiliserez Zend_Http_Client.
Donc, avec l'utilisation de ce nouveau client, nous avons implémenté une partie de l'API de Twitter. L'envoi d'un message de statut s'effectue via une requête POST à http://twitter.com/statuses/update.format (ou format devrait être converti en json, xml, atom ou rss) qui va inclure le paramètre " status " (dans le corps du POST) avec notre tweet comme valeur. Après la réception de la réponse, nous allons faire une petite vérification pour voir si l'opération s'est déroulée avec succès ou échouée et on redirige à nouveau l'utilisateur vers index.php.
Le dernier fichier que n'avons pas encore évoqué est lié tout en bas du formulaire d'index.php, clear.php est un script simple qui supprime le jeton d'accès courant et déclenche un autre processus d'autorisation Oauth en rappel via Twitter (juste au cas où vous l'avez initialement omis).
<?php
// Inclure le code commun
include_once './common.php'
;
// Effacer le jeton d'accès pour forcer le protocole OAuth de se relancer
$_SESSION
[
'TWITTER_ACCESS_TOKEN'
]
=
null
;
//Redirection à l'index et l le protocole devrait s'exécuter de nouveau.
header('Location: '
.
URL_ROOT .
'/index.php'
);
Maintenant, allez faire quelques tweets, dans tout autre client Twitter, le nom de l'application que vous avez enregistrée sera apposé à côté de votre nouveau tweet.
V. Autres Considérations sur OAuth ?▲
Il y a quelques choses que je voudrais mentionner avant de clôturer. Premièrement, Zend_Oauth implémente la Révision A de la spécification (1.0a) OAuth qui corrige une vulnérabilité relative à la fixation de session dans la spécification originale. Il n'est pas nécessaire de s'inquiéter à ce sujet car 1.0 est en cours de déploiement dans les autres fournisseurs de services. Chez Twitter le déploiement s'est fait en juin, le composant (Zend_Oauth) reste compatible avec la spécification originale pour les fournisseurs de services qui n'ont pas encore mis à jour leurs implémentations en toute discrétion. Zend_Oauth utilise en interne Zend_Crypt (aussi dans l'incubateur)(2) ainsi, nous offrons un support complet pour lehachage HMAC et RSA par le biais de ses sous-composants.
VI. Conclusion▲
Bien, vous l'avez votre petite application pour publier des tweets en utilisant l'API de Twitter par le biais Oauth. Manifestement, c'est un exemple assez simple, mais je pense qu'il est suffisamment clair pour scruter le mécanisme de fonctionnement sans confusion pour quiconque. En production les jetons ont besoin d'être maniés avec beaucoup plus de soin puisqu'ils restent valides pour de longues périodes. Vous devez aussi vous assurer d'associer les jetons aux utilisateurs adéquats.
Amusez-vous avec OAuth !