Accueil > OpenID Connect OAuth Serveur dédié > Développer > Contrôle d’accès HTTP (CORS)

Contrôle d’accès HTTP (CORS)

Les requêtes HTTP de type multi-origines (Cross-Origin Resource Sharing) sont des requêtes pour des ressources localisées sur un domaine différent de celui qui est à l’origine de la requête.

C’est exactement la configuration dans laquelle nous nous trouvons quand nous voulons appliquer les protocoles d’authentification à la communication entre applications réparties sur Internet.

Du fait de nombreux abus (attaques de type cross-site request forgery ou CSRF), les requêtes HTTP croisées ont été sujettes à des restrictions pour des raisons de sécurité bien compréhensibles.

Le but de cet article et d’expliquer comment franchir ces restrictions en toute légitimité et sécurité dans le cadre de la mise en oeuvre du service OAuth Server by DnC.

Références :
- Contrôle d’accès HTTP
- Cross-Origin Resource Sharing
- Les attaques de type cross-site request forgery

Partage de ressources avec requête multi-origines

Il est de nombreuses configurations dans lesquelles les hôtes de l’application et les points de terminaison des services ( ressources protégées (RS) ou API ) se situent sur des serveurs différents. Les applications dont les ressources sont réparties dans le Web (Public Cloud) constituent un bon exemple de cette configuration.

En utilisation Web normale, cette approche multi-hôte ou, plus précisément, "multi-origines" est restreinte pour des raisons de sécurité. Les règles de partage de ressources multi-origines (Cross-Origin Resource Sharing, CORS) permettent de répondre à ce type de requêtes en toute sécurité.

Différents cas de figure

Comme toujours lorsque nous abordons la question de la communication entre applications réparties sur différents domaines, nous devons d’abord distinguer deux cas :
- les serveurs de ressources protégées (RS) sont interrogés par des applications clientes identifiées à l’avance ; c’est toujours le cas lorsque les applications et les RS appartiennent à une même organisation ;
- les RS n’ont pas une connaissance préalable des applications.

Le premier cas est trivial. L’autorisation des requêtes multi-origines (Cross-Origin Resource Sharing) pourra être donnée, par exemple si le service est publié par un serveur http Apache ou compatible, dans le fichier .htaccess du RS :

##### CORS pour monsite.com :
SetEnvIf Origin "^http(s)?://(.+\.)?monsite\.com$" origin_is=$0
Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is

Seul le deuxième cas mérite notre attention.

Autorisation après validation du jeton d’identité

Dans le deuxième cas (l’application cliente n’est pas connue du RS) notre solution consiste à inscrire le champ Access-Control-Allow-Origin dans la réponse du serveur de ressource (RS) si, et seulement si, le jeton a été validé auprès du serveur d’authentification. Cela semble la bonne méthode, puisque le RS s’en remet au serveur d’authentification pour réaliser toutes les vérifications nécessaires, et n’a donc pas besoin de connaître au préalable le domaine d’origine de la requête [1] [2].

Voici un exemple de fonction d’autorisation en PHP pour SPIP 3.1, dans laquelle le RS valide le jeton par introspection (voir : Validation du jeton d’accès par interrogation du point d’extrémité "resource") :

  1. /*
  2. Autorisation avec OAuth Server by DnC
  3. Auteur : Bertrand degoy
  4. Copyright (c) 2016 DnC
  5. */
  6.  
  7. function oauth_authorize($accesstoken) {
  8.  
  9.     if ( !empty( $idtoken ) ) {
  10.  
  11.         // Interroger OAuth Server by DnC
  12.         include_spip('inc/distant');
  13.         // Vérifier le jeton d'identité (ID token)
  14.         $url = "https://oa.dnc.global/introspect.php?token=" .  
  15.                  $idtoken&requester_ip=$_SERVER['REMOTE_ADDR'];
  16.         $res = recuperer_url($url);  
  17.         if ( (int)$res['status'] === 200 ) {
  18.             if ( isset($_SERVER["HTTP_ORIGIN"]) ) {
  19.                 // Accès HTTP (CORS) : autoriser l'origine
  20.                 $issuer = trim(strtr($_SERVER["REMOTE_ADDR"], '<>"\'', '[]##'));    
  21.                 header('Access-Control-Allow-Origin', $issuer);
  22.             }
  23.             return true ;
  24.         }
  25.  
  26.     } else return false;
  27.  
  28. }    

Télécharger

Sécurité :
Notez que l’IP $_SERVER[’REMOTE_ADDR’] est passée à l’introspection par le paramètre ’requester_ip’ et sera comparée à l’IP de l’application cliente enregistrée sur le serveur. Il n’y a donc pas de risque de répondre à une application étrangère. Voyez : Vérification de l’origine de la requête reçue par un serveur de ressource.

Tester :
http://chemindeleau.com/?page=test-rpc-3 (utilise la fonction ProtectedApi_GET_auto() décrite ici : Une fonction pour tout simplifier !).

Cet exemple s’appuie sur l’API HTTP REST de Radar. Le site Chemin de l’eau est un site opérationnel qui met en œuvre des fonctionnalités issues des prototypes OAuthSD et Radar.

Message d’erreur : Blocage d’une requête multi-origines

"Blocage d’une requête multi-origines (Cross-Origin Request) : la politique « Same Origin » ne permet pas de consulter la ressource distante située sur http://r.dnc.global/http.api/collectionjson/gis?lat=47.8655638&lon=5.3314162&dmax=10&token=. Raison : échec du canal de pré-vérification des requêtes CORS."

La requête suivante, bien que le type indiqué soit ’GET’, est négociée avec la méthode ’OPTIONS’. Il en résulte que la requête cross-site est de type requête prévérifiée ("preflighted requests") :

  1. <div id="result"></div>
  2.  
  3. <script>
  4.  
  5.     $.ajax({
  6.        
  7.         url: 'http://r.dnc.global/http.api/collectionjson/gis',
  8.         dataType: 'json',
  9.         type: 'GET',
  10.         contentType: 'application/json',
  11.         data: 'lat=47.8655638&lon=5.3314162&dmax=10&token=#SESSION{auth_token}',
  12.         timeout: 15000,
  13.         success: function(data) {
  14.             var text = '<b>Requete : ' + data.collection.href + '</b><br /><br />';
  15.             items = data.collection.items;
  16.             for (var i=0; i < items.length; i++ ) {
  17.                 for (var j=0; j < items[i].data.length; j++ ) {
  18.                     text += items[i].data[j].name + '=' + items[i].data[j].value + "<br />";
  19.                 }
  20.                 text += "<br />";    
  21.             }
  22.             $("#result").html(text);
  23.  
  24.         },
  25.         error: function(oops) {
  26.             // oops.statusText returns error
  27.             $("#result").html(oops.statusText);
  28.              
  29.         }
  30.     });
  31.  
  32. </script>

Télécharger

Cliquez sur la vignette pour voir la requête et la réponse :

(Article à compléter. Mais est-ce pertinent ? L’utilisation de script expose le jeton, ce n’est pas la méthode recommandée).

Notes

[1Voici une démonstration claire de la différence entre autorisation et authentification : le serveur de ressource va répondre sans rien savoir a priori de l’identité de l’application cliente ni de l’utilisateur final. OpenID Connect permet de réaliser l’authentification, donc de répondre (ou non) en fonction de l’identité de l’application, de l’utilisateur final, voire des portées d’autorisation (scopes).

[2Ceci ne veut pas dire que le RS répond à n’importe quelle application qui présente le jeton, voir : Vérification de l’origine de la requête reçue par un serveur de ressource.