Bienvenue à la Construire votre démarrage avec la série PHP, qui est guider les lecteurs à travers le lancement d'une start-up réelle, Planificateur de réunion. Chaque épisode détaille différents problèmes de codage et d’affaires, avec des exemples détaillés que vous pouvez utiliser pour apprendre.
Récemment, je vous ai présenté la génération simple d'API REST de Yii et la nouvelle API de service "RESTful" de Meeting Planner. À ce moment-là, j'ai mentionné que ces API n'étaient que faiblement sécurisées. Bien sûr, il y avait un secret partagé entre le client et le serveur, mais il y avait quelques problèmes.
Tout d'abord, la clé secrète et les jetons utilisateur ont été transmis à plusieurs reprises dans les paramètres de requête d'appels SSL. Et il n'y avait pas d'autre vérification d'authenticité pour les données, permettant une attaque par l'intermédiaire.
Dans l'épisode d'aujourd'hui, je vais vous expliquer comment j'ai sécurisé l'API contre ces faiblesses pour une API plus robuste..
Si vous avez lu notre série de startups, vous avez probablement déjà déjà essayé Meeting Planner et Simple Planner, mais sinon, faites-le. Planifier une réunion est facile:
Comme d'habitude, je participerai aux commentaires ci-dessous, alors merci de donner votre avis. Vous pouvez également me joindre sur Twitter @lookahead_io. Je suis toujours particulièrement intrigué si vous souhaitez proposer de nouvelles fonctionnalités ou de nouveaux sujets pour de futurs tutoriels..
Pour rappel, tout le code pour Meeting Planner est écrit dans le framework Yii2 pour PHP. Si vous souhaitez en savoir plus sur Yii2, consultez notre série parallèle Programmer avec Yii2.
Commençons par jeter un regard sur la sécurité des API que j’avais codée. Nous présumerons qu'il y a une application mobile que j'ai partagée un $ app_id
et $ app_secret
avec. Seuls les appelants API avec ces clés sont acceptés.
Par exemple, l'application essaie d'enregistrer son propriétaire, probablement un nouvel utilisateur de Meeting Planner:
fonction publique actionRegister ($ app_id = ", $ app_secret =", $ source = ", $ firstname =", $ lastname = ", $ email =", $ oauth_token = ") Yii :: $ app-> response-> format = Response :: FORMAT_JSON; // verify app_id et app_key if (! Service :: verifyAccess ($ app_id, $ app_secret)) // to do - error msg return false;
L'application appelle ce qui précède actionRegister
via https://api.meetingplanner.io/user-token/register/ avec les arguments suivants:
$ app_id
et $ app_secret
pour l'authentification$ source = 'facebook'
pour le service OAuth que nous utilisons et accompagnons $ oauth_token
de ce service$ email
, $ prenom
, et $ nom
fourni via OAuthTous sont des arguments de requête tels que:
https://api.meetingplanner.io/user-token/register/?app_id=777&app_secret=imwithher&source=facebook&oauth_toker=zuckerburger&email=tom@macfarlins.com&firstname=thomas&lastname=macfarlins
Service :: verifyAccess ($ app_id, $ app_secret)
cherche les clés pour authentifier l'appel comme indiqué ci-dessous:
Le service de classe étend le modèle fonction statique publique verifyAccess ($ app_id, $ app_secret) if ($ app_id == Yii :: $ app-> params ['app_id'] && $ app_secret == Yii :: $ app-> params [ 'app_secret']) Yii :: $ app-> params ['site'] ['id'] = SiteHelper :: SITE_SP; retourne vrai; else return false;
Comme les clés et les données ont été envoyées via SSL, elles sont plutôt sécurisées mais non invincibles. La clé secrète sur les iPhones des utilisateurs n'est pas sûre non plus..
Comment pouvons-nous rendre cela plus sûr? Voici quelques idées:
Ce sont en fait des pratiques standard utilisées pour sécuriser les API.
Remarque: L'email et le jeton Facebook OAuth constituent un exemple de risque de transmission de données susceptibles d'être exposées dans les journaux du serveur. S'ils se trouvent dans les journaux, ils peuvent être utilisés avec l'API Facebook pour accéder au compte Facebook d'une personne..
Tout d'abord, je vais arrêter de transmettre le $ app_secret
. Au lieu de cela, nous allons signer les données sortantes avec elle avant de passer un appel API.
Nous allons donc alphabétiser les variables et les concaténer dans une chaîne, comme ceci:
$ data = $ email. $ prenom. $ dernier nom. $ oauth_token. $ source;
Résultant en:
$ data = '[email protected]'
Ensuite, nous allons hacher les données avec hash_hmac de PHP et le sha256
algorithme utilisant notre clé secrète.
$ signature = hash_hmac ('sha256', $ data, Yii :: $ app-> params ['app_secret']);
Cela crée un code de hachage unique basé sur les arguments de l'appel API et notre clé secrète partagée:
$ signature => 9f6d2f7dd7d674e85eff51f40f5f830787c37d84d4993ac9ccfea2800285bd02
Maintenant, nous pouvons appeler l'API sans transmettre la clé secrète. Au lieu de cela, nous transmettons la signature des données hachées ci-dessus.
J'utilise Postman pour tester l'API, mais vous pouvez également utiliser cURL:
Voici le code de l'API destinataire qui a répondu à l'appel ci-dessus:
fonction publique actionRegister ($ app_id = ", $ source =", $ firstname = ", $ lastname =", $ email = ", $ oauth_token =", $ sig = ") Yii :: $ app-> response-> format = Response :: FORMAT_JSON; $ sig_target = hash_hmac ('sha256', $ email. $ prenom. $ dernier nom. $ oauth_token. $ source, Yii :: $ app-> params ['app_secret']); if ($ app_id ! = Yii :: $ app-> params ['app_id'] && $ sig == $ sig_target) return 'ça a fonctionné!'; Autre return 'a échoué!';
En outre, comme indiqué précédemment, chaque utilisateur reçoit son propre jeton lorsqu'il accède à Meeting Planner via l'API, par exemple. via leur téléphone portable. Ainsi, après l'enregistrement, nous pouvons signer des appels avec leur jeton individuel et nous n'avons pas besoin de transmettre la clé secrète de l'application ni le jeton individuel de l'utilisateur..
Ensuite, nous allons migrer l'envoi des données dans les en-têtes. Vous pouvez le faire facilement avec Postman ou cURL. Voici le facteur:
Et voici cURL:
fonction publique actionCurl ($ sig) $ ch = curl_init (); curl_setopt ($ ch, CURLOPT_URL, "http: // localhost: 8888 / mp-api / jeton-utilisateur / register? sig =". $ sig); curl_setopt ($ ch, CURLOPT_RETURNTRANSFER, true); $ headers = ['app_id:'. 'imwithher', 'email:' .'tom @ macfarlins.com ',' prénom: '.'thomas', 'nom:' .'macfarlins ',' oauth_token: '.' zuckerburger ',' source: '.'facebook',]; curl_setopt ($ ch, CURLOPT_HTTPHEADER, $ en-têtes); $ server_output = curl_exec ($ ch); var_dump ($ server_output); curl_close ($ ch);
Voici le code de réception qui récupère les données de l'API à partir des en-têtes HTTPS:
fonction publique actionRegister ($ sig = ") Yii :: $ app-> réponse-> format = Response :: FORMAT_JSON; $ headers = Yii :: $ app-> request-> en-têtes; $ email = $ en-têtes-> get ('email'); $ prenom = $ headers-> get ('prénom'); $ lastname = $ headers-> get ('prenom'); $ oauth_token = $ headers-> get ('oauth_token'); $ source = $ headers-> get ('source'); if ($ headers-> a ('app_id')) $ app_id = $ headers-> get ('app_id'); $ sig_target = hash_hmac ('sha256', $ email. $ prenom. $ dernier nom. $ oauth_token. $ source, Yii :: $ app-> params ['app_secret']); if ($ app_id! = Yii :: $ app-> params ['app_id'] && $ sig == $ sig_target) return 'ça a fonctionné!'; else return 'failed!';
Nous avons commencé aujourd'hui avec les objectifs suivants:
Et nous avons atteint tous ces objectifs en ne modifiant que modestement notre code API. C'était amusant de faire ces changements et de voir avec quelle facilité nous pouvons mieux sécuriser une API. J'espère que vous avez apprécié suivre l'épisode d'aujourd'hui.
Je surveille régulièrement les commentaires, alors veuillez vous joindre à la discussion. Vous pouvez également me joindre directement sur Twitter @lookahead_io. Et bien sûr, surveillez les didacticiels à venir ici dans la série Construire son démarrage avec PHP.
Si vous ne le faisiez pas plus tôt, essayez de planifier une réunion dans Meeting Planner et dites-moi ce que vous en pensez. J'apprécie particulièrement les demandes de fonctionnalités.