Je développe une API REST qui nécessite une authentification. Étant donné que l'authentification se fait via un service Web externe via HTTP, je me suis dit que nous allions distribuer des jetons pour éviter d'appeler de manière répétée le service d'authentification. Ce qui m'amène à ma première question:
Est-ce vraiment mieux que de simplement demander aux clients d'utiliser HTTP Basic Auth à chaque demande et de mettre en cache les appels sur le serveur du service d'authentification?
La solution Basic Auth présente l’avantage de ne pas nécessiter un aller-retour complet vers le serveur avant que les demandes de contenu ne puissent commencer. Les jetons peuvent potentiellement avoir une portée plus flexible (c'est-à-dire octroyer uniquement des droits sur des ressources ou des actions particulières), mais cela semble plus approprié au contexte OAuth qu'à mon cas d'utilisation plus simple.
Actuellement, les jetons sont acquis comme ceci:
curl -X POST localhost/token --data "api_key=81169d80...
&verifier=2f5ae51a...
×tamp=1234567
&user=foo
&pass=bar"
Les api_key
, timestamp
et verifier
sont requis par toutes les demandes. Le "vérificateur" est renvoyé par:
sha1(timestamp + api_key + shared_secret)
Mon intention est de n'autoriser que les appels de tiers connus et d'éviter que les appels ne soient réutilisés tels quels.
Est-ce suffisant? Underkill? Overkill?
Avec un jeton en main, les clients peuvent acquérir des ressources:
curl localhost/posts?api_key=81169d80...
&verifier=81169d80...
&token=9fUyas64...
×tamp=1234567
Pour le plus simple appel possible, cela semble un peu horriblement prolixe. Considérant que le shared_secret
finira par être intégré à (au moins) une application iOS, dont je supposerais qu'elle peut être extraite, offre-t-il même autre chose qu'un faux sentiment de sécurité?
Permettez-moi de tout séparer et de résoudre chaque problème séparément:
Authentification
Pour l'authentification, baseauth présente l'avantage d'être une solution mature au niveau du protocole. Cela signifie que beaucoup de "pourraient surgir plus tard" , les problèmes sont déjà résolus pour vous. Par exemple, avec BaseAuth, les agents utilisateurs savent que le mot de passe est un mot de passe et ne le mettent donc pas en cache.
Charge du serveur d'authentification
Si vous distribuez un jeton à l'utilisateur au lieu de mettre en cache l'authentification sur votre serveur, vous continuez à faire la même chose: mettre en cache les informations d'authentification. La seule différence est que vous confiez la responsabilité de la mise en cache à l'utilisateur. Cela semble être une tâche inutile pour l'utilisateur, sans aucun gain. Je vous recommande donc de gérer cela de manière transparente sur votre serveur, comme vous l'avez suggéré.
Sécurité de la transmission
Si vous pouvez utiliser une connexion SSL, c'est tout ce que vous avez à faire, la connexion est sécurisée *. Pour éviter les exécutions multiples accidentelles, vous pouvez filtrer plusieurs URL ou demander aux utilisateurs d'inclure un composant aléatoire ("nonce") dans l'URL.
url = username:[email protected]/api/call/nonce
Si cela n'est pas possible et que les informations transmises ne sont pas secrètes, je vous recommande de sécuriser la demande avec un hachage, comme vous l'avez suggéré dans l'approche par jeton. Puisque le hachage fournit la sécurité, vous pouvez demander à vos utilisateurs de fournir le hachage en tant que mot de passe de base. Pour améliorer la robustesse, je recommande d'utiliser une chaîne aléatoire au lieu de l'horodatage comme "nonce" pour empêcher les attaques par rejeu (deux demandes légitimes pourraient être faites pendant la même seconde). Au lieu de fournir des champs distincts "secret partagé" et "clé api", vous pouvez simplement utiliser la clé api comme secret partagé, puis utiliser un sel qui ne change pas pour empêcher les attaques de la table Rainbow. Le champ nom d'utilisateur semble être un bon endroit pour mettre le nonce aussi, puisqu'il fait partie de l'auth. Alors maintenant, vous avez un appel clair comme celui-ci:
nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:[email protected]/api/call
C'est vrai que c'est un peu laborieux. En effet, vous n'utilisez pas de solution de niveau protocole (telle que SSL). Il pourrait donc être judicieux de fournir une sorte de SDK aux utilisateurs afin qu’ils ne soient pas obligés de les parcourir eux-mêmes. Si vous devez le faire de cette façon, je trouve le niveau de sécurité approprié (just-right-kill).
Stockage secret sécurisé
Cela dépend de ceux que vous essayez de contrecarrer. Si vous empêchez les personnes ayant accès au téléphone de l'utilisateur d'utiliser votre service REST au nom de l'utilisateur, il serait alors judicieux de rechercher une sorte d'API de trousseau sur le système d'exploitation cible et de disposer du SDK ( ou l’implémenteur) stocke la clé à cet endroit. Si cela n'est pas possible, vous pouvez au moins rendre un peu plus difficile l'obtention du secret en le cryptant et en stockant les données cryptées et la clé de cryptage à des endroits distincts.
Si vous essayez d'empêcher d'autres éditeurs de logiciels d'obtenir votre clé API pour empêcher le développement de clients alternatifs, seule l'approche chiffrer et stocker séparément fonctionne presque . Il s’agit d’une crypto whitebox, et à ce jour, personne n’a trouvé de solution vraiment sécurisée aux problèmes de cette classe. Le moins que vous puissiez faire est de toujours émettre une clé unique pour chaque utilisateur afin que vous puissiez interdire les clés maltraitées.
(*) EDIT: connexions SSL ne devrait plus être considéré comme sécurisé sans prendre des mesures supplémentaires pour vérifier eux.
Une API RESTful pure doit utiliser les fonctionnalités standard du protocole sous-jacent:
Pour HTTP, l'API RESTful doit être conforme aux en-têtes standard HTTP existants. L'ajout d'un nouvel en-tête HTTP enfreint les principes REST. Ne réinventez pas la roue, utilisez toutes les fonctionnalités standard des normes HTTP/1.1 - y compris les codes de réponse, les en-têtes, etc. Les services Web RESTFul doivent tirer parti des normes HTTP.
Les services RESTful DOIVENT être STATELESS. Toute astuce, telle qu'une authentification par jeton qui tente de mémoriser l'état des précédentes demandes REST sur le serveur, enfreint les principes REST. Encore une fois, c'est un MUST; Autrement dit, si votre serveur Web enregistre toute information liée au contexte de requête/réponse sur le serveur en vue d'établir une session sur le serveur, votre service Web N'EST PAS sans état. Et s'il n'est PAS apatride, il n'est PAS RESTFUL.
Conclusion: à des fins d'authentification/autorisation, vous devez utiliser l'en-tête d'autorisation standard HTTP. C'est-à-dire que vous devez ajouter l'en-tête autorisation/authentification HTTP dans chaque requête ultérieure devant être authentifiée. L'API REST doit respecter les normes du schéma d'authentification HTTP. Les détails de la mise en forme de cet en-tête sont définis dans les normes RFC 2616 HTTP 1.1 - section 14.8 Autorisation de RFC 2616 et dans l'authentification HTTP RFC 2617. : Authentification d'accès de base et Digest.
J'ai développé un service RESTful pour l'application Cisco Prime Performance Manager. Recherchez dans le document REST API que j'ai écrit pour cette application pour plus de détails sur la conformité de l'API RESTFul ici . Dans cette implémentation, j'ai choisi d'utiliser le schéma d'autorisation "basique" HTTP. - extrayez la version 1.5 ou supérieure de ce document API [REST] et recherchez une autorisation dans le document.
Sur le Web, un protocole avec état est basé sur le fait qu'un jeton temporaire est échangé entre un navigateur et un serveur (via un en-tête de cookie ou une réécriture d'URI) à chaque demande. Ce jeton est généralement créé sur le serveur et il s’agit d’une donnée opaque qui a une certaine durée de vie et qui a pour seul objectif d’identifier un agent d’utilisateur Web spécifique. En d'autres termes, le jeton est temporaire et devient un ETAT que le serveur Web doit gérer pour le compte d'un agent d'utilisateur client pendant la durée de la conversation. Par conséquent, la communication utilisant un jeton de cette manière est STATEFUL. Et si la conversation entre le client et le serveur est STATEFUL, elle n'est pas RESTful.
Le nom d'utilisateur/mot de passe (envoyé sur l'en-tête Authorization) est généralement conservé dans la base de données dans le but d'identifier un utilisateur. Parfois, l'utilisateur peut vouloir dire une autre application; Cependant, le nom d'utilisateur/mot de passe est NEVER destiné à identifier un agent d'utilisateur de client Web spécifique. La conversation entre un agent Web et un serveur basée sur l'utilisation du nom d'utilisateur/mot de passe dans l'en-tête Authorization (à la suite de l'autorisation de base HTTP) est STATELESS, car l'interface frontale du serveur Web ne crée ni ne maintient aucun STATE information que ce soit au nom d’un agent utilisateur de client Web spécifique. Et, selon ma compréhension de REST, le protocole indique clairement que la conversation entre les clients et le serveur doit être STATELESS. Par conséquent, si nous voulons avoir un véritable service RESTful, nous devons utiliser un nom d'utilisateur/mot de passe (voir RFC mentionné dans mon précédent message) dans l'en-tête Authorization pour chaque appel, PAS un type de jeton de type sension (par exemple, des jetons de session créés dans des serveurs Web. , OAuth jetons créés dans des serveurs d'autorisation, etc.).
Je comprends que plusieurs fournisseurs appelés REST utilisent des jetons tels que OAuth1 ou OAuth2 accept-tokens pour être passés en tant que "Authorization: Bearer" dans les en-têtes HTTP. Cependant, il me semble que l'utilisation de ces jetons pour des services RESTful violerait le véritable sens STATELESS, qui signifie que REST embrasse; parce que ces jetons sont temporaires données créées/maintenues côté serveur pour identifier un agent d'utilisateur de client Web spécifique pendant la durée de validité d'une telle conversation client/serveur Web. Par conséquent, aucun service utilisant ces jetons OAuth1/2 ne doit pas s'appeler REST si nous voulons nous en tenir à la signification VRAIE d'un protocole STATELESS.
Rubens