Authentification basée sur les jetons de l'API REST

122

Je développe une API REST qui nécessite une authentification. Étant donné que l'authentification elle-même se produit via un service Web externe sur HTTP, j'ai pensé que nous distribuerions des jetons pour éviter d'appeler à plusieurs reprises le service d'authentification. Ce qui m'amène à ma première question:

Est-ce vraiment mieux que d'exiger simplement que les clients utilisent l'authentification de base HTTP à chaque demande et la mise en cache des appels côté serveur du service d'authentification?

La solution Basic Auth a l'avantage de ne pas nécessiter un aller-retour complet vers le serveur avant que les demandes de contenu puissent commencer. Les jetons peuvent potentiellement avoir une portée plus flexible (c'est-à-dire n'accorder des droits qu'à des ressources ou des actions particulières), mais cela semble plus approprié au contexte OAuth que mon cas d'utilisation plus simple.

Actuellement, les jetons sont acquis comme ceci:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

Les api_key, timestampet verifiersont 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 provenant de parties connues et d'éviter que les appels ne soient réutilisés textuellement.

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...
                    &timestamp=1234567

Pour l'appel le plus simple possible, cela semble horriblement verbeux. Étant donné que la shared_secretvolonté finira par être intégrée (au minimum) dans une application iOS, à partir de laquelle je suppose qu'elle peut être extraite, est-ce même offrir quelque chose au-delà d'un faux sentiment de sécurité?

Cantlin
la source
2
Au lieu d'utiliser sha1 (timestamp + api_key + shard_secret), vous devriez utiliser hmac (shared_secret, timpestamp + api_key) pour un meilleur hachage de sécurité en.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco
@ MiguelA.Carrasco Et semble être le consensus en 2017 que bCrypt est le nouvel outil de hachage.
Shawn

Réponses:

94

Permettez-moi de tout séparer et de résoudre chaque problème de manière isolée:

Authentification

Pour l'authentification, baseauth a l'avantage d'être une solution mature au niveau du protocole. Cela signifie que de nombreux problèmes «pourraient surgir plus tard» 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 afin de ne pas le mettre 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 faites toujours 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 un travail inutile pour l'utilisateur sans gains, je 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 qu'il y a à 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 recommande de sécuriser la demande avec un hachage, comme vous l'avez suggéré dans l'approche par jeton. Étant donné que le hachage fournit la sécurité, vous pouvez demander à vos utilisateurs de fournir le hachage comme mot de passe de base. Pour une meilleure robustesse, je recommande d'utiliser une chaîne aléatoire au lieu de l'horodatage comme "nonce" pour éviter les attaques de relecture (deux requêtes légitimes pourraient être faites pendant la même seconde). Au lieu de fournir des champs séparés "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 table arc-en-ciel. Le champ du nom d'utilisateur semble également être un bon endroit pour mettre le nonce, car il fait partie de l'authentification. 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 (comme SSL). Il peut donc être judicieux de fournir une sorte de SDK aux utilisateurs pour qu'ils n'aient au moins pas à le 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 qui 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 judicieux de trouver une sorte d'API de trousseau de clés sur le système d'exploitation cible et de laisser le SDK (ou l'implémenteur) stocker le clé là-bas. Si ce 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 dans des endroits séparés.

Si vous essayez d'empêcher d'autres éditeurs de logiciels d'obtenir votre clé API pour empêcher le développement d'autres clients, seule l'approche chiffrer et stocker séparément fonctionne presque . Il s'agit de 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 seule clé pour chaque utilisateur afin de pouvoir interdire les clés abusées.

(*) EDIT: les connexions SSL ne doivent plus être considérées comme sécurisées sans prendre des mesures supplémentaires pour les vérifier .

cmc
la source
Merci cmc, tous les bons points et bonne matière à réflexion. J'ai fini par adopter une approche token / HMAC similaire à celle dont vous avez discuté ci-dessus, un peu comme le mécanisme d'authentification de l' API S3 REST .
cantlin
Si vous mettez en cache le jeton sur le serveur, n'est-ce pas essentiellement le même que le bon ancien identifiant de session? L'identifiant de session est de courte durée et il est également attaché au stockage de cache rapide (si vous l'implémentez) pour éviter de frapper votre base de données à chaque demande. La véritable conception RESTful et sans état ne devrait pas avoir de sessions, mais si vous utilisez un jeton comme ID et que vous frappez toujours la base de données, ne serait-il pas préférable d'utiliser simplement un ID de session à la place? Vous pouvez également opter pour des jetons Web JSON qui contiennent des informations chiffrées ou signées pour des données de session entières pour une véritable conception sans état.
JustAMartin
16

Une API RESTful pure doit utiliser les fonctionnalités standard du protocole sous-jacent:

  1. 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 d'état, les en-têtes, etc. Les services Web RESTFul doivent exploiter et s'appuyer sur les normes HTTP.

  2. Les services RESTful DOIVENT être SANS STATE. Toute astuce, telle que l'authentification basée sur un jeton qui tente de se souvenir de l'état des demandes REST précédentes sur le serveur, enfreint les principes REST. Encore une fois, c'est un must; autrement dit, si votre serveur Web enregistre des informations relatives au contexte de demande / réponse sur le serveur pour tenter d'établir une session quelconque sur le serveur, alors votre service Web n'est PAS sans état. Et s'il n'est PAS apatride, il n'est PAS RESTFul.

En bout de ligne: à des fins d'authentification / d'autorisation, vous devez utiliser l'en-tête d'autorisation standard HTTP. Autrement dit, vous devez ajouter l'en-tête d'autorisation / d'authentification HTTP dans chaque demande ultérieure qui doit être authentifiée. L'API REST doit suivre les standards du schéma d'authentification HTTP.Les spécificités du formatage de cet en-tête sont définies dans les standards RFC 2616 HTTP 1.1 - section 14.8 Autorisation de la RFC 2616, et dans la RFC 2617 Authentification HTTP: authentification d'accès de base et Digest .

J'ai développé un service RESTful pour l'application Cisco Prime Performance Manager. Recherchez sur Google le document de l'API REST 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 HTTP "Basic". - consultez la version 1.5 ou supérieure de ce document de l'API REST et recherchez l'autorisation dans le document.

Rubens Gomes
la source
8
"L'ajout d'un nouvel en-tête HTTP viole les principes REST" Comment cela? Et si vous y êtes, vous pouvez avoir la gentillesse d'expliquer quelle est exactement la différence (en ce qui concerne les principes) entre un mot de passe qui expire après une certaine période et un jeton qui expire après une certaine période.
un meilleur oliver
6
Le nom d'utilisateur + mot de passe est un jeton (!) Qui est échangé entre un client et un serveur à chaque demande. Ce jeton est conservé sur le serveur et a une durée de vie. Si le mot de passe expire, je dois en acquérir un nouveau. Vous semblez associer "jeton" à "session serveur", mais c'est une conclusion non valide. Ce n'est même pas pertinent car ce serait un détail de mise en œuvre. Votre classification des jetons autres que le nom d'utilisateur / mot de passe comme étant avec état est purement artificielle, à mon humble avis.
un meilleur oliver
1
Je pense que vous devriez motiver pourquoi faire une implémentation avec RESTful sur l'authentification de base qui fait partie de la question originale. Peut-être que vous pourriez également créer un lien vers de bons exemples avec du code inclus. En tant que débutant dans ce sujet, la théorie semble assez claire avec beaucoup de bonnes ressources mais la méthode de mise en œuvre ne l'est pas et les exemples sont compliqués. Je trouve frustrant qu'il semble nécessiter un codage personnalisé pour implémenter en temps opportun quelque chose qui a été fait des milliers de fois.
JPK le
13
-1 "Toute astuce, telle que l'authentification basée sur des jetons, qui tente de se souvenir de l'état des demandes REST précédentes sur le serveur enfreint les principes REST." L'authentification basée sur les jetons n'a rien à voir avec l'état des demandes REST précédentes et ne viole pas l'apatridie de REST .
Kerem Baydoğan
1
Donc, selon cela, les jetons Web JSON sont une violation REST car ils peuvent stocker l'état de l'utilisateur (revendications)? Quoi qu'il en soit, je préfère violer REST et utiliser le bon vieil identifiant de session comme "jeton", mais l'authentification initiale est effectuée avec nom d'utilisateur + passe, signée ou chiffrée en utilisant un secret partagé et un horodatage de très courte durée (donc cela échoue si quelqu'un essaie de rejouer cette). Dans une application «d'entreprise», il est difficile de se débarrasser des avantages de la session (en évitant de frapper la base de données pour certaines données nécessaires à presque toutes les requêtes), nous devons donc parfois sacrifier le véritable apatridie.
JustAMartin
2

Dans le Web, un protocole avec état est basé sur le fait d'avoir un jeton temporaire qui 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éé du côté du serveur.Il s'agit d'un élément de données opaque qui a une certaine durée de vie et qui a pour seul but d'identifier un agent utilisateur Web spécifique. Autrement dit, le jeton est temporaire et devient un ÉTAT que le serveur Web doit maintenir au nom d'un agent utilisateur client pendant la durée de cette 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 d'autorisation) 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 n'est JAMAIS destiné à identifier un agent utilisateur 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 (après l'autorisation HTTP de base) est SANS STATE car le serveur frontal du serveur Web ne crée ni ne gère aucune information STATEque ce soit pour le compte d'un agent utilisateur client Web spécifique. Et sur la base de ma compréhension de REST, le protocole indique clairement que la conversation entre les clients et le serveur doit être SANS STATE. Par conséquent, si nous voulons avoir un vrai service RESTful, nous devons utiliser un nom d'utilisateur / mot de passe (reportez-vous à la RFC mentionnée dans mon article précédent) dans l'en-tête d'autorisation pour chaque appel, PAS un type de jeton de tension (par exemple, des jetons de session créés dans des serveurs Web , Jetons OAuth créés dans les serveurs d'autorisation, etc.).

Je comprends que plusieurs fournisseurs REST appelés utilisent des jetons tels que les jetons d'acceptation OAuth1 ou OAuth2 à transmettre en tant que «Authorization: Bearer» dans les en-têtes HTTP. Cependant, il me semble que l'utilisation de ces jetons pour les services RESTful violerait la véritable signification STATELESS que REST embrasse; car ces jetons sont des données temporaires créées / gérées côté serveur pour identifier un agent utilisateur client Web spécifique pendant la durée valide d'une conversation client / serveur Web. Par conséquent, tout service qui utilise ces jetons OAuth1 / 2 ne doit pas être appelé REST si nous voulons nous en tenir à la signification TRUE d'un protocole STATELESS.

Rubens

Rubens Gomes
la source