Je lis le PDF gratuit de Apigee à propos du design des API web. Si les autres PDF gratuits du site sont assez creux, celui là pose de bonnes questions qui font écho avec mes propres reflexions.
Je le prends dans le désordre et pour reprendre mes erreurs passées ou celles que j’ai vu chez les autres :
- Pas de système de session avec point d’entrée spécifique pour le login. Ça demande au client de se préoccuper de l’expiration de la session et de son maintient. Pour des appels isolés ça veut dire faire deux requêtes (login + action) au lieu d’une, avec un délai de réponse finale allongé et une charge plus importante pour le serveur. Sauf besoin spécifique, il faut rester en stateless : Chaque connexion contient ses propres informations d’authentification.
- Pas d’authentification par IP, comme je le vois trop souvent. Outre que c’est un potentiel problème de sécurité, c’est juste quelque chose de difficilement maintenable et c’est toujours au dernier moment quand on veut faire un correctif, une migration ou une bascule vers le serveur de secours en urgence qu’on se rend compte du problème.
- L’authentification HTTP Digest me semble être une mauvaise réponse à tous les problèmes. Pour améliorer légèrement la résistance aux interceptions, il faut stocker le mot de passe en clair côté serveur. Une authentification HTTP Basic avec du TLS pour sécuriser la communication me semble bien plus pertinent, et aussi plus simple à réaliser.
- Le système fait maison est toujours la pire solution, même si vous pensez savoir ce que vous faites. C’est un NO GO commun à toute problématique qui touche la sécurité. Vous avez plus de chances de vous tirer une balle dans le pied qu’autre chose, et pour le même prix ce sera toujours plus complexe quand vous communiquerez avec des tiers.
- OAuth 2 a la mauvaise idée d’être plus une boite à outils qu’une solution finie. Même les gros groupes se prennent les pieds dans le tapis avec ça. On rejoint un peu le système fait maison. OAuth a ses défauts, mais globalement est une sphère contrôlée que vous devriez préférer.
Au final il reste le choix entre l’authentification HTTP Basic, l’authentification par certificat client avec SSL/TLS, ou OAuth 1.0. Ma grille de choix est la suivante :
- OAuth s’il s’agit d’avoir une authentification à trois pattes. Hors de question d’imposer à l’utilisateur final de saisir ses mots de passes dans un logiciel tiers. Pour une API qui veut créer un écosystème de logiciels clients (type twitter) c’est le choix quasiment imposé. Oui il y a des difficultés pour le mobile ou pour ce qui n’est pas « navigateur », mais ces questions sont désormais largement documentées. Pensez bien que choisir ce type d’authentification demande un réel travail (par exemple trouver l’ergonomie pour permettre à l’utilisateur d’autoriser et retirer l’autorisation d’applications tierces sur votre propre système)
- HTTP Basic par défaut pour quasiment toutes les autres situations. C’est simple côté client, simple et maitrisé côté serveur, supporté partout et pour tout, suffisamment sécurisé si on passe par du SSL/TLS.
- Et les certificats clients avec SSL/TLS ? C’est une solution qui semble plus intéressante que l’authentification HTTP mais qui se révèle complexe pour pas mal d’interlocuteurs. La valeur ajoutée ne semble pas valoir la complexité supplémentaire si vous n’interagissez pas avec des entreprises de taille significative. J’y croyais beaucoup, mais finalement j’ai peu d’argument face à la simplicité du HTTP Basic.
Et vous ? vous utilisez quoi pour l’authentification de vos services ?
18 réponses à “Définir son API : authentification”
J’ai fait le choix de l’OAuth2 qui correspond à mes besoins, de plus il est simple de l’utiliser côté client.
À voir, quand l’éditeur des spec OAuth considère que la v2 est un excellent moyen de se mettre une balle dans le pied et que même les gros se sont planté une ou deux fois côté sécu dans leur implémentation, j’ai tendance à me méfier.
Il y a un doc de 70 pages lié à la spec, uniquement pour la sécurité. Si tu trouves ça simple, tu as peut être oublié quelque chose. http://tools.ietf.org/html/draft-ietf-oauth-v2-threatmodel-08
Slt,
Juste une précision, HTTP Digest ne force pas le stockage en clair du mot de passe. Il requiert juste de stocker le mot de passe + le sel sous une forme particulière qui correspond au hash HA1 ( http://en.wikipedia.org/wiki/Digest_access_authentication ).
Personnellement, j’ai choisi de stocker sous cette forme afin d’être compatible avec Digest tout en gardant la sécurité de mots de passes cryptés : https://github.com/nfroidure/Rest4/blob/master/php/class.RestAuthDigestDriver.php#L59
Le seul inconvénient, c’est que tu ne peux pas faire varier le sel ou l’algo en fonction des clients. En fait, c’est possible pour l’algo, mais dans ce cas, ça impose de tester les différents algos successivement.
Oui mais non. Cf justement le lien dans Wikipedia : https://tools.ietf.org/html/rfc2617#section-4.13
« » »The security implications of this are that if this password file is
compromised, then an attacker gains immediate access to documents on
the server using this realm. Unlike, say a standard UNIX password
file, this information need not be decrypted in order to access
documents in the server realm associated with this file. » » »
et
« » »There are two important security consequences of this. First the
password file must be protected as if it contained unencrypted
passwords, because for the purpose of accessing documents in its
realm, it effectively does. » » »
En gros, celui qui lit ton mot de passe stocké peut accéder à ta ressource.
C’est chiffré dans le sens « si ton mot de passe est utilisé pour plusieurs ressources et que le realm utilisé pour les autres ressources est différent, alors l’attaquant ne pourra pas utiliser ce qu’il a trouvé pour accéder aux autres ressources ». Reste que toi tu l’as bien dans l’os. Il est très probable que ce n’est pas la sécurité que tu attendais, et probablement pas celle dont tu as besoin.
C’est à ça que sert le paramètre nonce que tu envois avec la demande d’authentification et que tu peux changer à chaque requête si tu veux. Mais ce dernier n’entre pas en compte pour le calcul de HA1.
Dans les faits, je n’ai pas encore trouvé la solution idéale car c’est pas vraiment « stateless », tu dois bien stocker ce nonce quelque part si tu veux que la requête d’authentification soit comprise, mais c’est bien possible.
Je ne sais comment mieux préciser mais si tu veux vérifier l’égalité des mots de passe tu peux :
* faire un hachage chez toi, récupérer une entrée en clair (ou déchiffrable), et refaire le procédé de hachage pour comparer
* récupérer une version hachée, stocker en clair (ou déchiffrable), et refaire le procédé de hachage pour comparer
* récupérer la même chose en distant et en local (ou déchiffrable de façon à ce que ça donne la même chose) et comparer
* On fait un système de signature avec chiffrage asymétrique (type RSA) et un challenge
* On utilise des mots de passe à usage unique (spip fait un truc de ce type, assez intelligent s’il n’y a pas de concurrence d’accès au login)
Le HTTP Basic est la première option. Le défaut de chiffrage du transport est compensé par TLS.
Le HTTP Digest est la seconde option. Il nécessite que le local soit déchiffrable. Pour compenser on considère que la clef n’est pas le mot de passe source mais un hachage avec le mot de passe source et le realm, ça ne change rien à la sécurité de la ressource elle-même (vu que ce qui permet l’accès est la clef, pas le mot de passe source) mais ça évite de griller d’autres ressources qui auraient le même mot de passe.
Re,
La démo vite fait :). Une authentification de type digest est basée sur la vérification d’une valeur nommée « response » dont la formule est :
HA1 = MD5(username:realm:password)
HA2 = MD5(method:digestURI)
response = MD5(HA1 : nonce : HA2)
Tu stockes en db HA1 ce qui implique que username, password, realm sont et restent toujours les mêmes.
Quand le client fait une demande :
GET /dir/index.html HTTP/1.0
Host: localhost
Tu lui envoie :
HTTP/1.0 401 Unauthorized
Server: HTTPd/0.9
Date: Sun, 10 Apr 2005 20:26:47 GMT
WWW-Authenticate: Digest realm= »MonRealm »,
qop= »md5″,
nonce= »dcd98b7102dd2f0e8b11d0f600bfb0c093″,
opaque= »5ccc069c403ebaf9f0171e9517f40e41″
Le client te renvoie :
GET /dir/index.html HTTP/1.0
Host: localhost
Authorization: Digest username= »Mufasa »,
realm= »MonRealm »,
nonce= »dcd98b7102dd2f0e8b11d0f600bfb0c093″,
uri= »/dir/index.html »,
qop=auth,
nc=00000001,
cnonce= »0a4f113b »,
response= »[valeur de response] »,
opaque= »5ccc069c403ebaf9f0171e9517f40e41″
Valeur de response vaut :
response = MD5(HA1 : nonce : HA2)
Tu récupère HA1 dans la base de donnée, tu reconstitues response avec la valeur nonce du client et la valeur de HA2, tu hashes et tu compares les hash.
Puisque le nonce est envoyé par tes soins à la demande d’authentification, response n’aura jamais la même valeur pour peu que tu prennes cette précaution. Tu peux, par exemple, baser nonce sur un timestamp unix valide
5 minutes après envoi.
Au final, tu as bien : un mot de passe hashé dans ta base de donnée ET une valeur de Authorization non réutilisable en cas d’interception par un tiers malveillant.
On revient à la question essentielle « Quel est le mot de passe ? »
Du point de vue utilisateur le mot de passe est une chaîne de caractère qui sert à calculer HA1. Du point de vue sécurité le vrai mot de passe c’est directement HA1 : Si tu connais HA1 tu peux t’authentifier sur le service (tu lances une requête pour récupérer un nonce et tu fais ton MD5(HA1:nonce:HA2)). L’attaquant n’a pas besoin de connaitre le mot de passe utilisateur.
Au final tu as bien sécurisé ton transport (celui qui intercepte ne peut pas se réauthentifier plus tard si tu empêche bien les attaques par replay) mais c’est au prix de retirer toute sécurité sur ton stockage local. Vu que tu stockes directement HA1 dans ta base et que ce HA1 est le seul mot de passe réel du point de vue sécurité, tu viens en fait de stocker ton mot de passe en clair dans ta base.
Le problème de cette authentification digest c’est qu’elle résout le mauvais problème. Pour sécuriser le transport TLS fonctionne relativement bien et répond au problème. L’essentiel des problèmes est plus lié au stockage local, et là tu es à poil.
La solution auth basic avec hachage du mot de passe sur le stockage serveur + TLS pour le transport n’est pas parfaite, mais elle répond bien mieux aux problématiques standard qu’on retrouve sur le web.
Je remets 10 francs ;).
Si je compromets ton serveur avec authentification basic, rien ne m’empêche de sniffer les connexions HTTP pour récupérer les champs Basic ce qui limite encore l’intérêt de Basic même avec TLS. En effet, il est possible d’intercepter le mot de passe en compromettant :
– le client
– la couche réseau
– et le serveur.
De plus, les conséquences sont plus grandes car elle ne se limitent pas au site hacké.
Pour Digest, la compromission ne peut être réalisée qu’au niveau du serveur et se limite au serveur lui même ce qui est pas trop dérangeant car vu que le serveur est compromis, un reset des mots de passe s’impose de toute façon.
Reste le problème des sauvegardes de la base qui peuvent être plus facilement divulguées par inadvertance. Bref, no silver bullet dans notre cas, à chacun de choisir la moins pire en âme et conscience.
* le client : si on compromet le client, tu es toujours dans la merde, quelle que soit ton authentification (sauf à déporter le calcul de la clef en dehors du client, avec un token externe par exemple)
* la couche réseau : côté http basic, la gestion est faite avec TLS – si ton client n’accepte pas n’importe quel certificat, tu es normalement correctement couvert (il y a toujours des failles dans les chaînes de certificat, mais si tu n’es pas un top100 international ou une organisation qui craint l’espionnage des gouvernements, normalement c’est plutôt bon)
* le serveur c’est toute la question. Si la personne arrive à intercepter tout le réseau en entrée ou contrôle toute ta machine tu es de toutes façons foutu. La question c’est en général de pouvoir limiter la casse sur des failles plus limitées. Le cas très classique c’est l’accès en lecture à du stockage de mot de passe. C’est plus que théorique car on voit passer régulièrement des news de ce type. La solution du http basic permet de palier ça.
Mais oui, il s’agit bien de faire une pondération des risques, suivant si tu souhaites plus protéger le réseau ou le serveur.
Pour l’attaque man in the middle, elle est très fréquente en entreprise. L’idée est qu’il mettent leur propres certificats racines dans le navigateur de leur employés et pour le coup, les salariés ne sont même pas au courant car peu au fait des problèmes de certificats. Ce genre de truc peuvent faire que si une personne accède à ton API depuis une boîte, son directeur peu avoir accès à son profil Facebook pour lequel elle a le même mot de passe.
Pour ça, de mémoire, il y a une extension Firefox qui permet justement d’être mis au courant quand c’est pas le même certificat racine qui a été employé que d’habitude.
[…] dans la logique de réfléchir son API, parce qu’un jour il faudra la faire évoluer, comment gérer le versionnement […]
Bonjour,
j’aime bien la réflexion ici.
J’ai bien compris Nicolas Froidure, personnellement, j’ai aussi un problème avec le stockage du « mot de passe » coté client et c’est là que j’aimerai bien comprendre edaspet.
«La solution auth basic avec hachage du mot de passe sur le stockage serveur + TLS pour le transport n’est pas parfaite, mais elle répond bien mieux aux problématiques standard qu’on retrouve sur le web.»
Ici, j’ai du mal, Auth Basic doit bien recevoir un mot de passe qui doit être stocké sur le client (c’est lui qui l’envoi)?
En quoi c’est différent HTTP Digest hormis l’aspect « plus complexe » des échanges qui sécurise au final que le transport.
Le client connait forcément le secret (mot de passe). C’est la base de l’authentification (c’est la connaissance du secret qui permet de différencier la personne légitime de l’usurpateur). Rien ne change ici quelle que soit la méthode.
La différence c’est que sur l’auth basique le serveur lui n’a pas besoin de stocker le secret sous une forme accessible. Il reçoit le secret en clair (modulo TLS) et peut donc lui appliquer un hachage (sens unique) pour le confronter avec sa version hachée stockée en local. Celui qui a accès à la version hachée du serveur ne peut pas s’en servir pour s’authentifier vu que ça ne lui permet pas de connaitre le secret à envoyer sur le réseau (sauf à tester toutes les combinaisons jusqu’à en trouver une qui donne le même résultat).
Sur l’auth digest c’est l’opération inverse qui est faite : le secret est stocké en clair sur le serveur. Ce dernier reçoit une version hachée qu’il ne peut décoder alors il hache sa propre version pour la confronter avec celle reçue. Celui qui a accès à ce qui est stocké sur le serveur peut s’en servir pour générer une authentification par le réseau.
Est-ce plus clair ?
Impect, merci beaucoup.
Ça répond parfaitement à ma question.
Même si le digest me semble indigeste pour le coup…
Pour résumé
– le serveur est hacké => qui est compromis?
Basic = personne
Digest = tout le monde
– le client est hacké => qui est compromis?
Basic = le client
Digest = personne
Evidemment, le serveur est sensé être la forteresse imprenable et là, le Digest est parfait car le client (qui est logiquement plus facile à compromettre) n’est pas une « faille »
En tout cas merci à vous deux, j’ai sous estimé les aspects sécurité et question qui pouvait ce poser (pour moi SSL/TLS c’était suffisant, alors que ça ne couvre qu’une couche réseaux)
Personnellement, je préfère le Basic (plus simple, mais pas plus simplicite)
Hello,
Et pour l’auth des utilisateurs sur des applications externe ?
L’auth basic a une limit, l’application connais le mot de passe…
Bien vu, il ya bien une sécurité plus faible en cas de compromission du serveur cependant la compromission se limite au site lui même. L’attaquant ne pourra pas accèder à d’autres sites pour lesquels les utilisateurs auront stocké le même mdp.
En revanche, pour basic, en cas d’attaque man in the middle, la sécurité du mot de passe lui même est compromise. Du coup, la question originale devient : faut-il faire plus confiance au serveur ou a la couche réseau.
le man in the middle de l’authentification basic est normalement gérée par la couche TLS justement