i-TegoDocumentationAuthentification > OpenID Connect OAuth Serveur dédié > Développer > OAuth 2.0 > Autorisation via un code (Authorization Code Grant)

OAuth 2.0 : Obtenir une autorisation pour l’application cliente

  publié le par i-Tego WM

Point d’extrémité d’autorisation (Authorization Endpoint)

https://oa.dnc.global/oauth/authorize.php
Le point d’extrémité d’autorisation est le point d’extrémité sur le serveur d’autorisation vers lequel l’application cliente redirige l’user-agent (en général un navigateur Web) pour obtenir une autorisation de la part de l’utilisateur final.

Forme de la demande d’autorisation

Voici un exemple en PHP avec SPIP :

SPIP

  1. // Interroger Authorize
  2. include_spip('inc/headers');
  3. $oauth_state = session_get('oauth_state');
  4. $url = "http://oa.dnc.global/oauth/authorize.php?response_type=code&
  5.           client_id=radar&state=$oauth_state";
  6. redirige_par_entete($url);

Télécharger

Notes :
- Le paramètre state, mentionné comme facultatif dans la spécification d’Oauth, est obligatoire sur OAuthSD afin d’éviter une faille de sécurité bien connue.
- Il est possible de rajouter à l’URL tout paramètre utile, comme un identificateur de session. Ceux-ci seront retransmis dans le corps de la réponse, de façon quasi intégrale (voir ci-après).
- Il est de la responsabilité de l’application cliente d’assurer la bonne forme et la sécurité des valeurs transmises par les paramètres d’URL.

A l’appel du Point d’extrémité d’autorisation :
- le serveur OAuth redirige l’user-agent vers la page d’authentification (on reste dans le domaine du serveur d’autorisation).
- le client s’authentifie dans cette page (donc sur le serveur d’authentification).
- le serveur poste le code d’autorisation au Point d’extrémité de redirection.

Voici un exemple de page d’authentification :

Notes :
- si l’on souhaite afficher la page d’authentification dans le contexte visuel de l’application cliente, il faut le faire dans un iFrame, plutôt que par une insertion en script côté client (Popup Javascript), afin de ne pas compromettre les identifiants de l’utilisateur. Cependant, il convient d’appliquer les bonnes pratiques de sécurité relatives à la mise en oeuvre des iFrame.

Retour à l’application cliente

En cas de succès, le serveur redirige le navigateur sur le point d’extrémité de redirection dans l’application cliente. Cet URI est défini par l’auteur d’une application cliente quand il l’inscrit sur ce serveur. Voir : OAuth 2.0 : Lier une application cliente au serveur OAuthSD.

Il est de la responsabilité de l’application cliente d’assurer sa sécurité vis-à-vis des valeurs transmises par les paramètres d’URL.

Exemple de code pour le Point d’extrémité de redirection

  publié le par i-Tego WM

L’écriture du code pour traiter le retour de l’autorisation est la partie la plus importante de l’adaptation d’une application cliente en vue d’accéder à une ressource protégée par OAuth. Cependant, il est possible d’écrire ce code pour qu’il s’adapte à tous cas de figure et qu’il puisse s’intégrer à tout développement ultérieur. L’investissement en vaut la peine !

Nous fournissons ici le squelette d’un tel code, en PHP, pour le flux Autorisation via un code (Authorization Code Grant).

Tous les cas non conformes ne sont pas traités, mais cependant le code est fonctionnel.
PHP

  1. <?php
  2.  
  3. /*
  4. Autorisation avec OAuth Server by DnC
  5.  
  6. Retour de OAuth authorize.
  7.  
  8. Ce fichier doit être installé du côté de l'application cliente.
  9. Il correspond au Point d’extrémité de redirection, ou URI de
  10. retour à l’application cliente (Redirection Endpoint).
  11.  
  12. Auteur : Bertrand degoy
  13. Copyright (c) 2016 DnC
  14. */
  15.  
  16.  
  17. //*
  18. $session_name = session_name();
  19. if ( empty($session_name) ) {
  20.     session_name('PHPSESSID');
  21. }
  22. if ( isset($_GET['state']) ) session_id($_GET['state']);
  23. //*/
  24.  
  25. // Code d'autorisation (Authorization code) retourné par le serveur
  26. $authcode = $_GET['code'];
  27. $sanitized_authcode = preg_replace('/[^a-f0-9"\']/', '', $authcode);  
  28.  
  29. if ( $sanitized_authcode === $authcode ) {  
  30.  
  31.     // Demander un jeton d'accès pour l'application
  32.     $jeton = '';
  33.  
  34.     $url = 'http://oa.dnc.global/oauth/token.php';
  35.  
  36.     $datas =  array(
  37.         'grant_type' => 'authorization_code',
  38.         'code' => $sanitized_authcode,
  39.         'client_id' => 'XXXXXXX',                        
  40.         'client_secret' => 'XXXXXXXXXXXXXX',  
  41.     );        
  42.  
  43.     $ch = curl_init();
  44.  
  45.     curl_setopt($ch, CURLOPT_URL, $url);
  46.     curl_setopt($ch, CURLOPT_POST, true);
  47.     curl_setopt($ch, CURLOPT_POSTFIELDS, $datas);
  48.     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  49.     curl_setopt($ch, CURLOPT_HEADER, false);
  50.     curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  51.     curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  52.  
  53.     $result_json = curl_exec($ch);
  54.     $result = json_decode($result_json, true);
  55.  
  56.     $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
  57.  
  58.     curl_close($ch);
  59.  
  60.     switch( (int)$http_code ) {
  61.         case 200 :
  62.  
  63.             if ( $result['access_token'] ) {
  64.                 $token = $result['access_token'];
  65.                 $sanitized_token = preg_replace('/[^a-f0-9"\']/', '', $token);
  66.                 if ( $sanitized_token === $token ) {
  67.                     $jeton = $token;      
  68.                 } else {
  69.                     // Jeton d'accès corrompu
  70.                 }      
  71.             } else {
  72.                 // Application non autorisée
  73.             }
  74.             if ( $result['refresh_token'] ) {
  75.                 $refresh_token = $result['refresh_token'];
  76.                 $sanitized_rtoken = preg_replace('/[^a-f0-9"\']/', '', $refresh_token);
  77.                 if ( $sanitized_rtoken === $refresh_token ) {
  78.                     $rejeton = $refresh_token;      
  79.                 } else {
  80.                     // Jeton de rafraîchissement corrompu
  81.                 }      
  82.             } else {
  83.                 // Application non autorisée
  84.             }
  85.  
  86.             if ( !empty($jeton) AND !empty($rejeton) ) {
  87.  
  88.                 // Ici, il serait bon de vérifier le jeton, par introspection par exemple.                
  89.                 ...
  90.                 // Succès, mémoriser les jetons dans la session de l'application
  91.                 $_SESSION['oauth_access_token'] = $jeton;
  92.                 $_SESSION['oauth_refresh_token'] = $rejeton;
  93.  
  94.                 if ( $oauth_again = $_SESSION['oauth_again'] ) {
  95.                     // Reprendre où nous en étions avant l'interruption
  96.                     header('Location: ' . $oauth_again );
  97.                     exit;
  98.                 }
  99.             }
  100.             break;
  101.  
  102.         case 401 :
  103.             // Accès non autorisé : problème avec CORS ?
  104.             break;
  105.         case 405 :
  106.             // Requête invalide
  107.             break;
  108.         case 400 :
  109.             // URL non trouvée ou Accès non autorisé
  110.         default:
  111.             break;
  112.     }
  113.  
  114.     if ( empty($jeton) ) {
  115.         // Lire l'erreur
  116.         if ( !is_null( $result ) ) {
  117.             $msg = json_decode( $res, true );
  118.             echo $msg['error'];         //DEBUG
  119.         }
  120.     }
  121.  
  122. } else {
  123.     // Code d'autorisation corrompu
  124. }
  125. ?>

Télécharger

Ceci n’est qu’un exemple très incomplet. Il est important de noter que le jeton ne sert à rien, puisque nous ne le validons pas. La seule chose que nous apprend cette procédure est que l’application est autorisée, à condition toutefois que la réponse ne soit pas falsifiée. La sécurité repose sur l’intégrité de la liaison application-serveur.

Une fonction pour tout simplifier !

  (publié initialement le dimanche 6 novembre 2016) par i-Tego WM

Du côté d’une application cliente, utiliser l’authentification OAuth pour interroger une API HTTP REST peut se réduire à une simple fonction.

Nous sommes dans le cas du flux Autorisation via un code (Authorization Code Grant).

Le diagramme ci-contre donne une idée simplifiée de la suite des échanges entre les 3 pôles d’un flux de type Autorisation via un code. La fonction ci-dessous correspond à la ligne "Client App". Le rafraîchissement du jeton d’accès qui apparaît dans le code a été omis dans le diagramme pour simplifier. Cliquez sur l’image pour l’agrandir


Voici un exemple en PHP pour SPIP 3.1 :
SPIP

  1. <?php
  2. /**
  3. * Interface avec OAuthSD, Autorisation via un code (Authorization Code Grant).
  4. * Cette fonction interroge une API protégée et retourne le résultat au format JSON.
  5. * Si l'API refuse l'accès, une demande d'authentification de l'utilisateur final est lancée automatiquement.  
  6. *
  7. * @param mixed $request : URI de l'appel à l'API REST
  8. * @return mixed string, bool : Résultat de la requête au format JSON ou false en cas de refus d'accès.
  9. */
  10. function ProtectedApi_GET_auto ( $request ) {
  11.  
  12.     include_spip('inc/headers');
  13.     include_spip('inc/distant');
  14.  
  15.     $token = $_SESSION['oauth_access_token'];
  16.     if ( !$token  ) {
  17.         if ( isset($_GET['token']) ) {
  18.             $token = $_GET['token'];
  19.         }
  20.     }
  21.  
  22.     if ( !empty($token) ) {
  23.  
  24.         // Interroger la ressource protégée
  25.  
  26.         $request_response = recuperer_url($request . "&token=$token");
  27.  
  28.         if ( (int)$request_response['status'] == 200 ) {
  29.             // Ok
  30.             return $request_response['page'];
  31.  
  32.         } elseif ( (int)$request_response['status'] == 401 ) {
  33.  
  34.             // Pas le droit d'accéder à cette ressource.
  35.             // Commencer par vérifier le jeton
  36.             $url = "http://oa.dnc.global/oauth/resource.php?access_token=" . $token;
  37.             $resource_response = recuperer_url($url);
  38.  
  39.             $status = (int)$resource_response['status'];
  40.             $page = json_decode($resource_response['page'], true);
  41.             $error = $page['error'];
  42.             $error_description = $page['error_description'];  
  43.  
  44.             switch( $status ) {
  45.  
  46.                 case 200 :
  47.                     // Le jeton était valide,ne pas boucler!
  48.                     return false;
  49.                     break;
  50.  
  51.                 case 401 :
  52.  
  53.                     if ( $error == 'expired_token' ) {
  54.                         // Rafraîchir le jeton expiré
  55.  
  56.                         // Interroger Token
  57.                         $options = array(
  58.                             'methode' => 'POST',
  59.                             'datas' => array(
  60.                                 'grant_type' => 'refresh_token',
  61.                                 'refresh_token' => $_SESSION['oauth_refresh_token'],
  62.                                 'client_id' => 'chemindeleau',
  63.                                 'client_secret' => '01fc4587ab1c23ff456e448dab18327a',
  64.                             ),      
  65.                         );
  66.  
  67.                         $url = "http://oa.dnc.global/oauth/token.php";
  68.                         $refresh_response = recuperer_url($url,$options);
  69.                         $refresh_status = (int)$refresh_response['status'];
  70.  
  71.                         if ( $refresh_status == 200 ) {
  72.  
  73.                             // Enregistrer le jeton d'accès et recommencer
  74.                             $page = json_decode($refresh_response['page'], true);
  75.                             $_SESSION['oauth_access_token'] = $page['access_token'];
  76.                             return ProtectedApi_GET_auto ( $request); // réentrer
  77.  
  78.                         } else
  79.                             // Demander une nouvelle authentification
  80.                             Authenticate();
  81.  
  82.                     } else return false;
  83.  
  84.                     break;
  85.  
  86.                 default :
  87.  
  88.                     // Demander une nouvelle authentification
  89.                     Authenticate();
  90.  
  91.             }
  92.             return false;
  93.         }
  94.     } else {
  95.  
  96.         // Demander une nouvelle authentification
  97.         Authenticate();
  98.  
  99.     }
  100.  
  101. }
  102.  
  103. /**
  104. * Demander une nouvelle authentification au serveur OAuth
  105. */
  106. function Authenticate() {
  107.    
  108.     // Sécurité : Générer un ID de session nouveau à chaque demande d'autorisation
  109.    
  110.     // Dire à Callback où il faudra reprendre après authentification
  111.     $_SESSION['oauth_again'] = $_SERVER['REQUEST_URI'];
  112.  
  113.     // Interroger Authorize
  114.     $url = "https://oa.dnc.global/oauth/authorize.php?response_type=code&client_id=" . APPNAME . "&state=" . session_id();
  115.     redirige_par_entete($url);    
  116.  
  117. }

Télécharger

 

L’utilisation de la fonction est très simple.
La fonction retourne un objet JSON représentant la réponse, que l’on peut facilement transformer en Array PHP :

  1. // Le nom de l'application cliente
  2. define ( 'APPNAME', 'testrpc3' );
  3. // Requete
  4. $request = "https://r.dnc.global/http.api/collectionjson/gis/27";
  5. // Afficher l'array obtenu
  6. echo print_r( json_decode( ProtectedApi_GET_auto ( $request ) ) , true );

Télécharger

Lancer un test

Dans ce test, les rôles sont les suivants :
- application cliente : chemindeleau,
- serveur de données protégées : API Rest de Radar,
- serveur d’authentification : oa.dnc.global,
- utilisateur final : vous.

Lancer le test

Vous devrez cliquer sur "Inscrivez-vous ici" pour être inscrit comme client final sur le serveur d’autorisation. Rappel : nous ne divulguons pas les E-mails.

Si vous ne voulez pas vous inscrire sur ce serveur en tant qu’utilisateur, vous pourrez utiliser les identifiants suivants :
E-mail or pseudo : bebert
Password : 012345678