Authentification REST et exposition de la clé API

93

J'ai lu sur REST et il y a beaucoup de questions sur SO à ce sujet, ainsi que sur beaucoup d'autres sites et blogs. Bien que je n'ai jamais vu cette question spécifique posée ... pour une raison quelconque, je ne peux pas comprendre ce concept ...

Si je construis une API RESTful et que je souhaite la sécuriser, l'une des méthodes que j'ai vues consiste à utiliser un jeton de sécurité. Lorsque j'ai utilisé d'autres API, il y a eu un jeton et un secret partagé ... a du sens. Ce que je ne comprends pas, c'est que les demandes à une opération de service de repos sont effectuées via javascript (XHR / Ajax), ce qui empêche quelqu'un de renifler cela avec quelque chose de simple comme FireBug (ou "afficher la source" dans le navigateur) et copier la clé API, puis emprunter l'identité de cette personne à l'aide de la clé et du secret?

tjans
la source
l'une des méthodes que j'ai vues est d'utiliser un jeton de sécurité , il existe vraiment beaucoup de méthodes. Avez-vous un exemple concret. Je pense peut-être que vous vous confondez avec "REST" et "rendre disponible une API javascript uniquement pour les utilisateurs enregistrés" (ex google maps).
PeterMmm
1
Puisque vous avez demandé il y a presque 2 ans: qu'avez-vous finalement utilisé vous-même?
Arjan
En fait, je n'ai rien utilisé, j'essayais plus simplement de me concentrer sur la création des concepts. Le commentaire de PeterMmm ci-dessus est probablement vrai ... je n'ai toujours pas eu besoin de mettre en œuvre tout cela, mais je voulais m'améliorer ... merci pour le suivi.
tjans le

Réponses:

22

api secret n'est pas transmis explicitement, secret est utilisé pour générer un signe de la demande en cours, côté serveur, le serveur génère le signe en suivant le même processus, si les deux signes correspondent, alors la demande est authentifiée avec succès - donc seul le le signe est passé à travers la demande, pas le secret.

James.Xu
la source
9
Donc, si c'est juste le signe qui est passé ... n'est-ce pas encore exposé en javascript ... donc si je mets une photo scintillante sur ma page Web via leur API (appelée par javascript), et que vous visitez ma page, aren ' t J'expose ma clé API à quiconque visite ma page?
tjans
6
Je ne pense pas que je pose ma question correctement ... probablement une partie de la raison pour laquelle je n'ai pas trouvé ce que je cherchais en premier lieu. quand je fais mon appel ajax, disons en utilisant jquery, je devrais intégrer la clé api dans l'appel ajax afin qu'elle soit transmise au serveur ... à ce stade, quelqu'un peut voir la clé API. Si je comprends mal, comment la clé API est-elle envoyée avec la demande si elle n'est pas intégrée dans le script client?
tjans
4
pour conclure: les gens se verront attribuer une paire apikey + apisecret avant d'utiliser un openapi / restapi, le signe apikey + sera transféré vers le serveur pour s'assurer que le serveur sait qui fait la demande, l'apisecret ne sera jamais transféré vers le serveur pour la sécurité .
James.Xu
7
Donc, la déclaration de @ James.Xu selon laquelle "le secret est utilisé pour générer un signe de la demande en cours" est FAUX! Parce que le client ne connaît pas le secret, car il serait dangereux de lui envoyer (et comment le saurait-il autrement?) Le `` secret '' qui est techniquement une `` clé privée '' est utilisé UNIQUEMENT PAR LE SERVEUR (parce que personne d'autre ne le sait) pour générer un signe à comparer au signe du client. Donc la question: quel type de données est combiné avec la «clé api» que personne d'autre ne connaît au-delà du client et du serveur? Sign = api_key + quoi?
ACs
1
Vous avez raison, @ACs. Même si les deux serveurs (le site Web et l'API tierce) connaissent le même secret, on ne peut pas calculer une signature sur le serveur du site Web, puis mettre ce résultat dans le HTML / JavaScript, puis faire en sorte que le navigateur le transmette à l'API. Ce faisant, tout autre serveur pourrait demander ce code HTML au premier serveur Web, extraire la signature de la réponse et l'utiliser dans le code HTML de son propre site Web. (Je pense vraiment que l'article ci-dessus ne répond pas à la question de savoir comment une clé API publique dans le HTML peut être sûre.)
Arjan
61

Nous exposons une API que les partenaires ne peuvent utiliser que sur les domaines qu'ils ont enregistrés chez nous. Son contenu est en partie public (mais de préférence pour être affiché uniquement sur les domaines que nous connaissons), mais est principalement privé pour nos utilisateurs. Alors:

  • Pour déterminer ce qui est affiché, notre utilisateur doit être connecté avec nous, mais cela est géré séparément.

  • Pour déterminer les données sont affichées, une clé API publique est utilisée pour limiter l'accès aux domaines que nous connaissons, et surtout pour garantir que les données des utilisateurs privés ne sont pas vulnérables à CSRF .

Cette clé API est en effet visible par n'importe qui, nous n'authentifions pas notre partenaire d'une autre manière, et nous n'avons pas besoin de REFERER . Pourtant, il est sécurisé:

  1. Lorsque notre get-csrf-token.js?apiKey=abc123est demandé:

    1. Recherchez la clé abc123dans la base de données et obtenez une liste de domaines valides pour cette clé.

    2. Recherchez le cookie de validation CSRF. S'il n'existe pas, générez une valeur aléatoire sécurisée et placez-la dans un cookie de session HTTP uniquement . Si le cookie existait, obtenez la valeur aléatoire existante.

    3. Créez un jeton CSRF à partir de la clé API et de la valeur aléatoire du cookie, puis signez-le . (Plutôt que de conserver une liste de jetons sur le serveur, nous signons les valeurs. Les deux valeurs seront lisibles dans le jeton signé, c'est très bien.)

    4. Définissez la réponse pour qu'elle ne soit pas mise en cache, ajoutez le cookie et renvoyez un script comme:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }

    Remarques:

    • Ce qui précède n'empêche pas un script côté serveur de simuler une requête, mais garantit uniquement que le domaine correspond si un navigateur le demande.

    • La même politique d'origine pour JavaScript garantit qu'un navigateur ne peut pas utiliser XHR (Ajax) pour charger puis inspecter la source JavaScript. Au lieu de cela, un navigateur ordinaire ne peut le charger qu'en utilisant <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(ou un équivalent dynamique), puis exécutera le code. Bien entendu, votre serveur ne doit pas prendre en charge le partage de ressources inter-origines ni JSONP pour le JavaScript généré.

    • Un script de navigateur peut modifier la valeur de document.domainavant de charger le script ci-dessus. Mais la même politique d'origine ne permet de raccourcir le domaine en supprimant des préfixes, comme la réécriture subdomain.example.comjuste example.com, ou myblog.wordpress.comà wordpress.com, ou dans certains navigateurs , même bbc.co.ukà co.uk.

    • Si le fichier JavaScript est récupéré à l'aide d'un script côté serveur, le serveur recevra également le cookie. Cependant, un serveur tiers ne peut pas obliger le navigateur d'un utilisateur à associer ce cookie à notre domaine. Par conséquent, un jeton CSRF et un cookie de validation qui ont été récupérés à l'aide d'un script côté serveur ne peuvent être utilisés que par des appels côté serveur ultérieurs, pas dans un navigateur. Cependant, ces appels côté serveur n'incluront jamais le cookie utilisateur et ne peuvent donc récupérer que des données publiques. Ce sont les mêmes données qu'un script côté serveur pourrait extraire directement du site Web du partenaire.

  2. Lorsqu'un utilisateur se connecte, définissez un cookie utilisateur de la manière que vous souhaitez. (L'utilisateur s'est peut-être déjà connecté avant la demande de JavaScript.)

  3. Toutes les demandes d'API ultérieures au serveur (y compris les demandes GET et JSONP) doivent inclure le jeton CSRF, le cookie de validation CSRF et (s'il est connecté) le cookie utilisateur. Le serveur peut maintenant déterminer si la demande doit être approuvée:

    1. La présence d'un jeton CSRF valide garantit que le JavaScript a été chargé à partir du domaine attendu, s'il est chargé par un navigateur.

    2. La présence du jeton CSRF sans le cookie de validation indique une falsification.

    3. La présence à la fois du jeton CSRF et du cookie de validation CSRF ne garantit rien: il peut s'agir d'une fausse requête côté serveur ou d'une requête valide d'un navigateur. (Il ne peut pas s'agir d'une demande d'un navigateur effectuée à partir d'un domaine non pris en charge.)

    4. La présence du cookie utilisateur garantit que l'utilisateur est connecté, mais ne garantit pas que l'utilisateur est membre du partenaire donné, ni que l'utilisateur consulte le bon site Web.

    5. La présence du cookie utilisateur sans le cookie de validation CSRF indique une falsification.

    6. La présence du cookie utilisateur garantit que la demande en cours est effectuée via un navigateur. (En supposant qu'un utilisateur n'entre pas ses informations d'identification sur un site Web inconnu, et en supposant que nous ne nous soucions pas des utilisateurs qui utilisent leurs propres informations d'identification pour faire une demande côté serveur.) Si nous avons également le cookie de validation CSRF, alors ce cookie de validation CSRF était également reçu à l'aide d'un navigateur. Ensuite, si nous avons également un jeton CSRF avec une signature valide, etle nombre aléatoire dans le cookie de validation CSRF correspond à celui de ce jeton CSRF, puis le JavaScript pour ce jeton a également été reçu lors de la même demande antérieure au cours de laquelle le cookie CSRF a été défini, donc également à l'aide d'un navigateur. Cela implique également que le code JavaScript ci-dessus a été exécuté avant la définition du jeton et qu'à ce moment-là, le domaine était valide pour la clé API donnée.

      Donc: le serveur peut désormais utiliser en toute sécurité la clé API du jeton signé.

    7. Si à un moment donné le serveur ne fait pas confiance à la demande, un 403 interdit est renvoyé. Le widget peut répondre à cela en affichant un avertissement à l'utilisateur.

Il n'est pas nécessaire de signer le cookie de validation CSRF, car nous le comparons au jeton CSRF signé. Ne pas signer le cookie rend chaque requête HTTP plus courte et la validation du serveur un peu plus rapide.

Le jeton CSRF généré est valable indéfiniment, mais uniquement en combinaison avec le cookie de validation, donc efficacement jusqu'à la fermeture du navigateur.

Nous pourrions limiter la durée de vie de la signature du jeton. Nous pourrions supprimer le cookie de validation CSRF lorsque l'utilisateur se déconnecte, pour répondre à la recommandation OWASP . Et pour ne pas partager le nombre aléatoire par utilisateur entre plusieurs partenaires, on pourrait ajouter la clé API au nom du cookie. Mais même dans ce cas, il n'est pas facile d'actualiser le cookie de validation CSRF lorsqu'un nouveau jeton est demandé, car les utilisateurs peuvent naviguer sur le même site dans plusieurs fenêtres, partageant un seul cookie (qui, lors de l'actualisation, serait mis à jour dans toutes les fenêtres, après quoi le Le jeton JavaScript dans les autres fenêtres ne correspondrait plus à ce cookie unique).

Pour ceux qui utilisent OAuth, voir également OAuth et Widgets côté client , dont j'ai eu l'idée JavaScript. Pour une utilisation côté serveur de l'API, dans laquelle nous ne pouvons pas nous fier au code JavaScript pour limiter le domaine, nous utilisons des clés secrètes au lieu des clés API publiques.

Arjan
la source
1
Lorsque vous utilisez CORS, peut - être , on peut en toute sécurité étendre cela. Au lieu de ce qui précède, lors du traitement d'une OPTIONSdemande pré-volée avec une clé API publique dans l'URL, le serveur peut indiquer à un navigateur quels domaines sont autorisés (ou annuler la demande). Attention cependant, certaines requêtes ne nécessitent pas de requête pré-volée, ou n'utiliseront pas du tout CORS , et que CORS a besoin d'IE8 +. Si une solution de secours Flash est utilisée pour IE7, alors peut-être qu'une certaine dynamique crossdomain.xmlpeut aider à obtenir la même chose pour cela. Nous n'avons pas encore essayé CORS / Flash.
Arjan
10

Cette question a une réponse acceptée, mais juste pour clarifier, l'authentification secrète partagée fonctionne comme ceci:

  1. Le client a une clé publique, cela peut être partagé avec n'importe qui, peu importe, vous pouvez donc l'intégrer dans javascript. Ceci est utilisé pour identifier l'utilisateur sur le serveur.
  2. Le serveur a une clé secrète et ce secret DOIT être protégé. Par conséquent, l'authentification par clé partagée nécessite que vous puissiez protéger votre clé secrète. Ainsi, un client javascript public qui se connecte directement à un autre service n'est pas possible car vous avez besoin d'un intermédiaire serveur pour protéger le secret.
  3. Le serveur signe la demande en utilisant un algorithme qui inclut la clé secrète (la clé secrète est un peu comme un sel) et de préférence un horodatage envoie ensuite la demande au service. L'horodatage sert à empêcher les attaques de "replay". La signature d'une demande n'est valable que pendant environ n secondes. Vous pouvez vérifier cela sur le serveur en obtenant l'en-tête d'horodatage qui doit contenir la valeur de l'horodatage inclus dans la signature. Si cet horodatage a expiré, la demande échoue.
  4. Le service reçoit la demande qui contient non seulement la signature mais également tous les champs qui ont été signés en texte brut.
  5. Le service signe ensuite la demande de la même manière en utilisant la clé secrète partagée et compare les signatures.
Chris
la source
C'est vrai, mais de par sa conception, votre réponse n'expose pas la clé API. Cependant, dans certaines API, la clé d'API est visible publiquement, et c'est de cela qu'il s'agissait: "requêtes à une opération de service de repos [...] effectuées via javascript (XHR / Ajax)" . (La réponse acceptée est fausse à ce sujet aussi, je pense; votre point 2 est clair à ce sujet, bien.)
Arjan
1

Je suppose que vous voulez dire la clé de session et non la clé API. Ce problème est hérité du protocole http et connu sous le nom de détournement de session . La «solution de contournement» normale consiste, comme sur tout site Web, à passer en https.

Pour exécuter le service REST sécurisé, vous devez activer https et probablement l'authentification du client. Mais après tout, cela dépasse l'idée REST. REST ne parle jamais de sécurité.

PeterMmm
la source
8
Je voulais vraiment dire la clé. Si je me souviens bien, pour utiliser une API, vous passez la clé API et le secret au service de repos pour s'authentifier, n'est-ce pas? Je sais qu'une fois passé sur le fil, il serait crypté par SSL, mais avant qu'il ne soit envoyé, c'est parfaitement visible par le code client qui l'utilise ...
tjans
1

Ce que vous voulez faire côté serveur, c'est générer un identifiant de session expirant qui est renvoyé au client lors de la connexion ou de l'inscription. Le client peut ensuite utiliser cet identifiant de session comme secret partagé pour signer les demandes suivantes.

L'identifiant de session n'est transmis qu'une seule fois et cela DOIT être via SSL.

Voir l'exemple ici

Utilisez un nonce et un horodatage lors de la signature de la demande pour éviter le détournement de session.

Iain Porter
la source
1
Mais comment peut-il y avoir une connexion lorsqu'un tiers utilise votre API? Si l'utilisateur veut se connecter, les choses sont simples: il suffit d'utiliser une session? Mais lorsque d'autres sites Web doivent s'authentifier auprès de votre API, cela n'aide pas. (En outre, cela sent beaucoup la promotion de votre blog.)
Arjan
1

Je vais essayer de répondre à la question dans son contexte d'origine. La question est donc "La clé secrète (API) peut-elle être placée en toute sécurité dans JavaScript?

À mon avis, c'est très dangereux car cela va à l'encontre du but de l'authentification entre les systèmes. Étant donné que la clé sera exposée à l'utilisateur, l'utilisateur peut récupérer des informations auxquelles il n'est pas autorisé. Parce que dans une communication de repos typique, l'authentification est uniquement basée sur la clé API.

Une solution à mon avis est que l'appel JavaScript passe essentiellement la demande à un composant serveur interne qui est responsable de faire un appel de repos. Le composant serveur interne, disons qu'un servlet lira la clé API à partir d'une source sécurisée telle qu'un système de fichiers basé sur des autorisations, l'insérera dans l'en-tête HTTP et effectuera l'appel de repos externe.

J'espère que ça aide.

Développeur MG
la source