API OpenFlyers: Difference between revisions

From Documentation de la solution web de gestion OpenFlyers
Jump to navigation Jump to search
imported>Lelhidam
 
(66 intermediate revisions by 5 users not shown)
Line 2: Line 2:
L'objet de cette page est de décrire l'API OpenFlyers.
L'objet de cette page est de décrire l'API OpenFlyers.


;Description de l'API
OpenFlyers possède une API basée sur [[Wikipedia-en:OAuth#OAuth_2.0|OAuth2]] qui permet à des serveurs extérieurs, dûment [[#Enregistrer-un-client|enregistrés]], de mettre en œuvre un processus d'[[Wikipedia-fr:Authentification_unique|authentification unique (SSO)]] et/ou de récupération des résultats des requêtes SQL de la [[Bibliothèque-des-rapports|bibliothèque des rapports]] ou des rapports personnalisés sous la forme de fichiers CSV.
OpenFlyers possède une API basée sur [[Wikipedia-en:OAuth#OAuth_2.0|OAuth2]] qui permet à des serveurs extérieurs, dûment [[#Enregistrer-un-client|enregistrés]], de mettre en œuvre un processus d'[[Wikipedia-fr:Authentification_unique|authentification unique (SSO)]] et/ou de récupération des résultats des requêtes SQL de la [[Bibliothèque-des-rapports|bibliothèque des rapports]] ou des rapports personnalisés sous la forme de fichiers CSV.
Un client de démonstration est disponible pour comprendre les mécanismes décrits ci-dessous.
L'utilisation de ce client de démonstration est décrite dans le chapitre [[#Client-de-démonstration|Client de démonstration]] de cette page.
Le client de démonstration est lui-même accessible à cette adresse : https://openflyers.com/oauth2-demo/index.php
Le code source du client de démonstration est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
L'utilisation du code source est décrite dans le chapitre [[#Configurer-le-client-à-partir-du-code-source|Configurer le client à partir du code source]].


OAuth2 propose plusieurs mécanismes pour permettre l'authentification. Un mécanisme d'authentification détermine la séquence exacte des étapes impliquées dans le processus d'authentification d'OAuth2. OpenFlyers met à disposition deux mécanismes d'authentification :
OAuth2 propose plusieurs mécanismes pour permettre l'authentification. Un mécanisme d'authentification détermine la séquence exacte des étapes impliquées dans le processus d'authentification d'OAuth2. OpenFlyers met à disposition deux mécanismes d'authentification :
* [[#Authorization Code|Authorization Code]] basé sur la méthode d'authentification par code d'autorisation et qui correspond au mécanisme associé à l'[[Wikipedia-fr:Authentification_unique|authentification unique (SSO)]],
*[[#Authorization Code|Authorization Code]] basé sur la méthode d'authentification par code d'autorisation et qui correspond au mécanisme associé à l'[[Wikipedia-fr:Authentification_unique|authentification unique (SSO)]],
* [[#Client Credentials|Client Credentials]] basé sur la méthode d'authentification avec les identifiants clients et qui est utilisé dans un contexte d'automatisme sans autorisation de l'utilisateur au préalable.
*[[#Client Credentials|Client Credentials]] basé sur la méthode d'authentification avec les identifiants clients et qui est utilisé dans un contexte d'automatisme sans autorisation de l'utilisateur au préalable.


Dans les chapitres qui suivent, le terme ''ressource'' fait référence à la définition OAuth2. Une ressource dans OAuth2 est un élément qui peut être :
Dans les chapitres qui suivent, le terme ''ressource'' fait référence à la définition OAuth2. Une ressource dans OAuth2 est un élément qui peut être :
* une ou des données comme des photos, des documents, des contacts ou des informations personnelles,
*une ou des données comme des photos, des documents, des contacts ou des informations personnelles,
* un ou plusieurs services comme des transferts de fonds, la récupération de rapports ou l'ajout d'articles sur un blog,
*un ou plusieurs services comme des transferts de fonds, la récupération de rapports ou l'ajout d'articles sur un blog,
* toute ressource nécessitant un accès restreint.
*toute ressource nécessitant un accès restreint.


OpenFlyers définit plusieurs [[#Utiliser-l'API|types de ressources]] :
OpenFlyers définit plusieurs [[#Utiliser-l'API|types de ressources]] :
* les [[#Récupérer-les-informations-de-l'utilisateur-connecté|informations de l'utilisateur connecté]],
*les [[#Récupérer-les-informations-de-l'utilisateur-connecté|informations de l'utilisateur connecté]],
* les [[#Récupérer les rapports génériques|rapports génériques]] ou [[#Récupérer les rapports personnalisés|personnalisés]] au format CSV.
*les [[#Récupérer les rapports génériques|rapports génériques]] ou [[#Récupérer les rapports personnalisés|personnalisés]] au format CSV.


OAuth2 dispose de ''scopes''. Un scope est un privilège définit de manière explicite permettant l'accès à une ressource protégée. OpenFlyers met à disposition une [[#Liste des scopes disponibles|liste de scopes]] utilisables à travers l'API.
OAuth2 dispose de ''scopes''. Un scope est un privilège définit de manière explicite permettant l'accès à une ressource protégée. OpenFlyers met à disposition une [[#Liste des scopes disponibles|liste de scopes]] utilisables à travers l'API.


Deux options ont été ajoutées à l'API OpenFlyers. La première correspond à [[#Authentification-TLS-Mutuelle-(mTLS)|mTLS]]. [[#Authentification-TLS-Mutuelle-(mTLS)|mTLS]] permet, en plus d'authentifier le serveur avec un certificat, d'authentifier le client avec un certificat TLS. Cette fonctionnalité permet d'éviter les usurpations d'identité. La seconde correspond à [[#HTTP-Signature|HTTP-Signature]]. [[#HTTP-Signature|HTTP-Signature]] permet de signer les en-têtes et le corps (lorsqu'il y en a un) des messages échangés afin d'en garantir leur intégrité.
Deux protocoles de sécurité sont présents dans l'API OpenFlyers :
*[[#Authentification-TLS-Mutuelle-(mTLS)|mTLS]] : il permet d'authentifier le client avec un certificat TLS, en plus d'authentifier le serveur avec un certificat. Ce protocole permet d'éviter les usurpations d'identité.
*[[#HTTP-Signature|HTTP-Signature]] : il permet de signer les en-têtes et le corps (lorsqu'il y en a un) des messages échangés afin d'en garantir leur intégrité.


=Procédures=
;Premiers pas - Client de démonstration
==Configurer le client à partir du code source==
Un client de démonstration est disponible pour comprendre les mécanismes décrits ci-dessous.
;Prérequis
Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php


;Télécharger Le code source du client de démonstration :
L'utilisation de ce client de démonstration est décrite dans la procédure [[#Utiliser-le-client|Utiliser le client]] de cette page.


Le code source est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
Le client de démonstration est lui-même accessible à cette adresse : https://openflyers.com/oauth2-demo/index.php


Le code source est structuré de la manière suivante :
Le code source du client de démonstration est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip


[[Fichier:Oauth2 src demo folder.png|800px]]
L'utilisation du code source est décrite dans la procédure [[#Créer-un-client-à-partir-du-code-source|Créer un client à partir du code source]].


* Le dossier '''css''' contient tout le matériel nécessaire à la stylisation de la page web.
=Définitions=
* Le dossier '''img''' contient les images affichées sur la page web.
==Authentification TLS Mutuelle (mTLS)==
* Le dossier '''ssl''' est le dossier contenant tous les certificats et les clé privées associées à chaque client. Il doit respecter une structure particulière décrite ci-dessous.
En général dans une communication TLS, seul le serveur a l'obligation de fournir un certificat. Il est également possible pour le client de fournir un certificat. Ce principe s'appelle l'authentification mutuelle et est mise en place avec Mutual TLS (ou [[Wikipedia-en:Mutual_authentication#mTLS|mTLS]]).
* Le fichier '''ClientDemo.php''' contient la classe '''ClientDemo'''. Cette classe contient toutes les méthodes nécessaires au fonctionnement du client de démonstration OAuth2.
* Le fichier '''index.php''' est le fichier à appeler depuis le navigateur. Ce fichier correspond au fichier qui gère les appels à la classe '''ClientDemo''' et exécute les méthodes dans l'ordre.


Le dossier '''ssl''' doit respecter la structure suivante.
OpenFlyers associe un certificat pour l'authentification mutuelle unique à chaque client OAuth2.


[[Fichier:Oauth2 demo tree.png]]
;Envoyer un certificat client
 
Côté client, le code suivant peut être utilisé pour fournir à cURL le certificat et la clé correspondante ainsi que le certificat du CA d'OpenFlyers à utiliser pour la connexion :
;[[#Générer des certificats|Générer les certificats]] :
<syntaxhighlight lang="php">
Après la génération des certificats, les clés privées 'auth.key' et 'sign.key' remplacent celles présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo'.
curl_setopt_array($request, [
    CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
    CURLOPT_CAINFO    => $caCertificatePath,
    CURLOPT_SSLCERT    => $certificatePath,
    CURLOPT_SSLKEY    => $keyPath
]);
</syntaxhighlight>
 
'''À noter''' : le certificat du CA d'OpenFlyers est nécessaire pour assurer la validité des certificats utilisés.
 
==HTTP Signature==
HTTP Signature utilise le principe de la signature numérique pour garantir l'authenticité et l'intégrité du message HTTP.


;[[#Enregistrer un client|Enregistrer les clients]] :
La signature est générée à l'aide d'une clé privée et vérifiée à l'aide de la clé publique correspondante ou d'un certificat contenant cette clé publique.


*Deux clients doivent être créés :
HTTP Signature utilise deux en-têtes HTTP :
**Le premier pour le mécanisme d'autorisation '''Authorization Code''',
*Signature : contient la signature et ses métadonnées.
**Le second pour le mécanisme d'autorisation '''Client Credentials'''.
*Digest : contient le corps du message haché.


[[Fichier:Oauth2 demo manage.png|800px]]
===Digest===
Le ''digest'' est calculé comme ceci : <code>digest = base64encode(sha256(corps du message))</code>


*Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton '''Télécharger le certificat CA''' de la page de gestion. Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton '''Télécharger le certificat de signature du serveur''' de la page de gestion. Placer les deux certificats téléchargés à la racine du dossier '''ssl'''.
Et l'en-tête est structuré de la manière suivante : <code>Digest: SHA-256=<digest></code>


*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Authorization Code''' et les placer dans le répertoire '''ssl/AuthCodeDemo'''.  
Un autre algorithme de hachage peut être utilisé, SHA-256 reste cependant le plus répendu.
*Modifier le fichier '''ssl/AuthCodeDemo/config.authcode.json''' en le remplissant de la manière suivante.
 
<php>{
;Exemple en php
  "client_id": "XXXXXXXXXXXXXXXX",
<syntaxhighlight lang="php">
  "client_secret": "XXXXXXXXXXXXXXXX",
$digestHeader = 'Digest: SHA-256=' . base64_encode(hash('sha256', $postData, true));
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
</syntaxhighlight>
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
 
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
===Signature===
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
L'en-tête HTTP de signature est structuré de la manière suivante : <code>Signature: keyId="<keyId>",algorithm="<algo>",headers="<signed_headers>",signature="<signature>"</code>
  "auth_cert": "path_to_client/ssl/AuthCodeDemo/auth_cert.crt",
 
  "auth_key": "path_to_client/ssl/AuthCodeDemo/auth.key",
Le champ ''keyId'' correspond à un identifiant permettant l'identification de la clé utilisée pour vérifier la signature. Pour l'API d'OpenFlyers, sa valeur correspond à l'empreinte SHA-1 du certificat au format PEM à utiliser pour vérifier la signature.
  "sign_cert": "path_to_client/ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}</php>


*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
L'algorithme ''algo'' correspond à celui utilisé pour générer la signature, exemple : <code>rsa-sha256</code>.
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.
*Remplacer <code>path_to_client/</code> par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
**Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/</code>.
**Exemple sur un serveur Linux '''debian''': <code>./</code>.


La valeur de ''signed_headers'' correspond à la liste des en-têtes inclus dans la signature séparés d'un espace. Exemple : <code>date digest (request-target)</code>


*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Client Credentials''' et les placer dans le répertoire '''ssl/ClientCredDemo'''.
Pour générer la signature, une chaîne de caractères appelée <code>signing string</code> contenant les en-têtes au format <code>lowercase_header_name: value</code> séparés par une nouvelle ligne au format LF (<code>\n</code>) est d'abord générée. Exemple avec les en-têtes "date" et "(request-target)" :
*Modifier le fichier '''ssl/ClientCredDemo/config.clientcred.json''' en le remplissant de la manière suivante.
<php>{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/ClientCredDemo/auth.key",
  "sign_cert": "path_to_client/ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/ClientCredDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}</php>


*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
<pre>(request-target): post /some/uri\ndate: Tue, 07 Jun 2014 20:51:35 GMT</pre>
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.
*Remplacer <code>path_to_client/</code> par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
**Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/</code>.
**Exemple sur un serveur Linux '''debian''': <code>./</code>.


La signature est ensuite générée comme ceci : <code>base64encode(algo(signing string))</code>


*Suivre la [[#Utiliser le client|procédure d'utilisation du client de démonstration]] pour faire fonctionner le client.
;Exemple en php
<syntaxhighlight lang="php">
function generateSignatureHeader(array $headersToSign, string $certificateFingerprint, string $privateKey): string
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers            .= $normalizedHeaderKey . ' ';
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }


==Enregistrer un client==
    // trim extra whitespace
Pour utiliser l'API OAuth2, il faut enregistrer un client OAuth2 auprès d'OpenFlyers. Pour ceci, suivre les étapes suivantes :
    $headers        = trim($headers);
    $signatureString = trim($signatureString);


    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, $privateKey, 'RSA-SHA256');
    $signature = base64_encode($signature);


;Pour le mécanisme d'authentification Client Credentials
    // compiling the header line
*Créer un [[Gestion-des-profils#Ajouter-un-profil|nouveau profil]]. Ce profil doit permettre de gérer les droits du client OAuth2. Choisir un nom explicite, par exemple "Client OAuth rapports".
    return "Signature: keyId=\"$certificateFingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}
</syntaxhighlight>
Les variables ''$certificateFingerprint'' et ''$privateKey'' correspondent respectivement à une empreinte SHA-1 de certificat et à une clé privée, tous deux au format PEM.  


*Sélectionner les droits à assigner à ce profil. Ces droits limitent les données auxquelles le client OAuth2 a accès.
La variable ''$headersToSign'' est un tableau formaté de la manière suivante :
**Sélectionner les droits relatifs à l'enregistrement de clients OAuth2 dans l'onglet '''Admin''' (colonne '''Associé aux clients OAuth2''').
<syntaxhighlight lang="php">
**Sélectionner les rapports accessibles par le profil précédemment créé.
[
    $headerName => $headerValue,
    ...
]
</syntaxhighlight>


*Créer un nouvel utilisateur à partir du panneau de gestion. Cet utilisateur est virtuel et représente le serveur sur lequel fonctionne le client OAuth2.
'''À noter''' : les en-têtes sont à signer côté client avec le certificat de signature signé par le CA d'OpenFlyers et dédié au client ainsi que la clé privée associée. Le certificat de signature dédié au client est téléchargeable depuis l'interface de gestion des clients OAuth2. Les en-têtes de la réponse du serveur quant à elles doivent être vérifiées avec le certificat de signature HTTP du serveur, téléchargeable aussi depuis l'interface de configuration des clients OAuth2.
**''Des identifiant et nom explicites sont recommandés (exemple : "serv1_oauth_client")''
**'''Attention''' : tout utilisateur désactivé et associé à un client OAuth2 rend le client inactif, il faut donc changer l'utilisateur associé au client pour réactiver le client.


=Client OAuth2=
Une fois le client OAuth2 configuré sur OpenFlyers, il faut l'utiliser avec un client créé au préalable pour communiquer avec le serveur d'autorisation et l'API.


;Pour le mécanisme d'authentification Authorization Code
Plusieurs [https://oauth.net/code/ bibliothèques] simplifiant la création d'un client sont disponibles.
*Aller dans '''Admin > Utilisateurs > Profils'''
*Dans l'onglet '''Généralités''', cocher la case relative à la colonne '''Connexion depuis l'extérieur (OAuth2)''' pour le profil souhaité.


Des scripts client basiques écrits en php sont aussi fournis pour les mécanismes [[#Script-client-:-authorization-code|''authorization_code'']] et [[#Script-client-:-client-credentials|''client_credentials'']]


;Créer un nouveau client OAuth2
==Authorization Code==
*Aller dans '''Admin > Imports > API OAuth2 > Paramètres'''
Ce flux OAuth2 se déroule en plusieurs étapes :
[[Fichier:Oauth2 manage.png|800px]]
*Le client redirige le navigateur de l'utilisateur vers l'URL d'autorisation.
*Cliquer sur le bouton Ajouter '''+''' ou '''Ajouter un client'''
*Le navigateur de l'utilisateur est redirigé vers l'URL fournie durant la demande ou durant l'enregistrement du client.
[[Fichier:Oauth2 client creation.png|800px]]
*Le client récupère un code d'autorisation grâce à la redirection précédente, et échange ce code contre un jeton d'accès auprès du serveur d'autorisation.
*Choisir un nom pour le client.
*Le client peut utiliser ce code d'accès :
*Sélectionner le mécanisme d'autorisation utilisé par le client :
**Comme preuve d'authentification pour une solution SSO (Single Sign-On).
**'''Authorization Code''': permet d'utiliser OAuth2 comme solution SSO ou accéder à des données utilisateurs. Cette méthode peut être couplée avec le mécanisme de mémorisation de connexion (''Refresh Token'').
**Pour accéder à des données sur le serveur distant en utilisant l'API.
**'''Client Credentials''': permet d'utiliser OAuth2 dans un contexte d'automatisme.
*Saisir l'URI de redirection vers le client pour le mécanisme ''Authorization Code''.
*Sélectionner l'utilisateur virtuel créé précédemment pour le mécanisme ''Client Credentials''.
*[[#Générer-des-certificats|Générer deux CSR]] afin d'obtenir deux certificats signés et les saisir :
**'''Certificate Signing Request pour le certificat d'authentification''' est utilisé pour l'authentification mutuelle avec mTLS.
**'''Certificate Signing Request pour le certificat de signature''' est utilisé pour la signature des en-têtes HTTP.
*Cliquer sur '''Enregistrer'''.




;Sauvegarder le couple ID/passphrase
    +-----------+                  +------+                +-----------+                  +-------------+                  +-----------+
Un couple ID/passphrase (client_id/client_secret) est généré. Ces deux clées ne sont communiquées qu'une seule fois. Elle doivent être stockées en toute sécurité et gardées confidentielles.
    |Utilisateur|                  |Client|                |Navigateur |                  |  Serveur  |                  |  Serveur  |
[[Fichier:Oauth2 client created.png]]
    +-----+-----+                  +--+---+                +-----+-----+                  |Autorisation |                  | Ressources|
 
          |                            |                          |                        +------+------+                  +-----+-----+
 
          |                            |                          |                              |                              |
;Remarques
          |                            |                  Demande|d'autorisation                |                              |
*Les certificats du CA d'OpenFlyers et de signature HTTP du serveur sont nécessaires. Ils sont téléchargeables depuis l'interface de configuration des clients OAuth2.
          |                            +------------------------->+------------------------------>|                              |
*Dans certains cas d'utilisation, il peut être nécessaire d'ajouter le certificat du CA d'OpenFlyers au Trust Store du système. Si cette étape n'est pas réalisée, les certificats peuvent être considérés comme invalides et peuvent ne pas être utilisables.
          |                            |                          |                              |                              |
*Pour ajouter le certificat CA au Trust Store du système, suivre les étapes suivantes:
          |                            |Authentification + Formulaire d'autorisation              |                              |
**Sous Linux, copier le certificat CA d'OpenFlyers dans le dossier <code>/usr/local/share/ca-certificates</code> et exécuter la commande <code>sudo update-ca-certificates</code>
          |<---------------------------+--------------------------+<------------------------------+                              |
**Sous Windows,
          |                            |                          |                              |                              |
***Double-cliquer sur le certificat CA d'OpenFlyers téléchargé depuis l'interface d'enregistrement des clients OAuth2
          |                            |      Autorisation        |                              |                              |
***Cliquer sur '''Installer un certificat...'''
          +----------------------------+------------------------->+------------------------------>|                              |
***Choisir l'emplacement de stockage (utilisateur ou ordinateur) et cliquer sur '''Suivant''' puis '''Suivant''' et enfin '''Terminer'''
          |                            |                          |                              |                              |
 
          |                            |                      Code|d'autorisation                |                              |
==Générer des certificats==
          |                            |<-------------------------+<------------------------------+                              |
L'API OAuth2 implémente [https://tools.ietf.org/html/draft-cavage-http-signatures-10 HTTP Signature] et l'[[Wikipedia-en:Mutual_authentication#mTLS|authentification TLS mutuelle]]. Ces mécanismes utilisent chacun une paire certificat/clé privée différente.
          |                            |                          |                              |                              |
 
          |                            |                Demande de|token                          |                              |
Pour obtenir ces certificats, il faut d'abord générer des Certificate Signing Request (CSR).
          |                            +--------------------------+------------------------------>|                              |
          |                            |                          |                              |                              |
          |                            |                Access (+|Refresh) token                |                              |
          |                            |<-------------------------+-------------------------------+                              |
          |                            |                          |                              |                              |
          |                            |                          |      Requête vers API       |                              |
          |                            +--------------------------+-------------------------------+------------------------------>|
          |                            |                          |                              |                              |
          |                            |                          |                              |                              |
          |                            |                          |                              |<------------------------------+
          |                            |                          |                              |  Vérification d'autorisation  |
          |                            |                          |                              +------------------------------>|
          |                            |                          |                              |                              |
          |                            |                          |     Données/Réponse          |                              |
          |                            |<-------------------------+-------------------------------+-------------------------------+
          |                            |                          |                              |                              |


La procédure est la suivante :
*Se connecter au serveur qui devra utiliser l'API.
*Naviguer dans le répertoire dans lequel les fichiers doivent être créés.
*Utiliser les deux fichiers de configuration ''sign_cert.conf'' et ''auth_cert.conf'' ci-dessous et les remplir.


;Fichier de configuration OpenSSL - sign_cert.conf
<pre>[req]
default_bits      = 4096                    # taille par défaut des nouvelles clés, peut être surchargé dans la commande
encrypt_key        = no                      # chiffrer la clé générée
distinguished_name = req_distinguished_name  # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                  # pointe vers la catégorie spécifiée pour les extensions x509
prompt            = no


[req_distinguished_name]
===Générer les codes pour PKCE===
C                  =                          # code à deux chiffres du pays (ex: FR)
Pour faire fonctionner le client OAuth2, il faut générer deux codes pour l'extension PKCE :
ST                =                         # région/état (ex: Gironde)
*Un ''code_verifier'' échangé pendant la demande de jeton d'accès.
L                  =                          # ville (ex: Bordeaux)
*Un ''code_challenge'' dérivé du ''code_verifier'' et échangé pendant la demande d'autorisation.
O                  =                          # organisation (ex: OpenFlyers)
 
OU                =                         # unité organisationelle (ex: IT)
;Générer le ''code_verifier''
CN                =                         # nom de domaine (ex: openflyers.com)
<syntaxhighlight lang="php">
function generateCodeVerifier($length = 128): string
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
    $outputCode = '';


[v3_req]
    for ($i = 0; $i < $length; $i++) {
keyUsage          = digitalSignature        # pour quelles opérations la clé peut-elle être utilisée</pre>
        $index      = random_int(0, strlen($characters) - 1);
        $outputCode .= $characters[$index];
    }


;Fichier de configuration OpenSSL - auth_cert.conf
    return $outputCode;
<pre>[req]
}
default_bits      = 4096                    # taille par défaut des nouvelles clés, peut être surchargé dans la commande
</syntaxhighlight>
encrypt_key        = no                      # chiffrer la clé générée
distinguished_name = req_distinguished_name  # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                  # pointe vers la catégorie spécifiée pour les extensions x509
prompt            = no


[req_distinguished_name]
Cette fonction permet de générer un ''code_verifier'' avec une longueur donnée. Le ''code_verifier'' doit avoir une longueur entre 43 et 128 caractères.
C                  =                          # code à deux chiffres du pays (ex: FR)
ST                =                          # région/état (ex: Gironde)
L                  =                          # ville (ex: Bordeaux)
O                  =                          # organisation (ex: OpenFlyers)
OU                =                          # unité organisationelle (ex: IT)
CN                =                          # nom de domaine (ex: openflyers.com)


[v3_req]
;Générer le ''code_challenge''
extendedKeyUsage  = clientAuth              # pour quelles opérations la clé peut-elle être utilisée</pre>
<syntaxhighlight lang="php">
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}
</syntaxhighlight>


Cette fonction génère le ''code_challenge'' à partir du ''code_verifier'' fourni.


Exécuter les commandes suivantes :
===Demande d'autorisation===
*<bash>openssl req -sha256 -newkey rsa -keyout sign.key -out sign_cert.csr.pem -outform PEM -config sign_cert.conf</bash>
Pour initier la demande d'autorisation, rediriger le navigateur de l'utilisateur vers l'URL : <code>GET https://openflyers.com/nom-de-plateforme/oauth/authorize.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
*<bash>openssl req -sha256 -newkey rsa -keyout auth.key -out auth_cert.csr.pem -outform PEM -config auth_cert.conf</bash>


Ces commandes prennent chacune en entrée le fichier de configuration et génèrent une clé privée et un Certificate Signing Request.  
Envoyer également les paramètres suivants :
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|response_type||string||Le type de réponse envoyée par le serveur. Doit utiliser la valeur "code" (sans guillements) pour ce mécanisme.
|-
|redirect_uri||string||L'URI (ou l'URL) fourni pendant l'enregistrement du client et vers lequel l'utilisateur est redirigé après la demande d'autorisation.
|-
|scope||string||[[#Liste-des-scopes-disponibles|Liste des droits demandés par le client]], séparés par des espaces.
|-
|state||string||Chaîne de caractère aléatoire utilisée pour éviter les [https://fr.wikipedia.org/wiki/Cross-site_request_forgery attaques CSRF]. '''Fortement recommandé'''.
|-
|code_challenge||string||Code nécessaire pour le fonctionnement de l'extension [[#Générer-les-codes-pour-PKCE|PKCE]].
|-
|code_challenge_method||string||Méthode utilisée pour générer le ''code_challenge''. Ici, la valeur est "S256".
|}


Une fois ces CSR obtenus :
===Demande de jeton d'accès===
*Les renseigner dans les champs prévus à cet effet lors de la [[#Enregistrer-un-client|création d'un client]] et télécharger les certificats signés depuis l'interface une fois le client créé.
Après avoir répondu à la demande, l'utilisateur est redirigé vers l'URI fourni pendant l'enregistrement du client. Si la demande est acceptée, un code temporaire : ''code'' est fourni, ainsi que le paramètre ''state'' fourni pendant la demande avec la même valeur. Si la demande est refusée, un code d'erreur est renvoyé.
*Garder la clé privée confidentielle. Une fuite poserait un risque de sécurité. Elle va de paire avec le certificat distribué par l'autorité de certification OpenFlyers.
;Si le paramètre ''state'' a une valeur différente de celle envoyée avec la demande, c'est peut-être une tentative d'attaque et il faut refuser la réponse.


Echanger ce ''code'' contre un jeton d'accès via l'URL : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.


Les paramètres suivants sont également nécessaires :
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|client_secret||string||La passphrase reçue pendant l'enregistrement du client.
|-
|code||string||Le code temporaire reçu dans la réponse à la demande d'autorisation.
|-
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, sa valeur doit être ''authorization_code''
|-
|redirect_uri||string||L'URI (ou l'URL) de redirection fourni pendant l'enregistrement du client.
|-
|code_verifier||string||Le ''code_verifier'' utilisé pour générer le ''code_challenge'' de la demande d'autorisation.
|}


==Utiliser le client==
Si la requête est correcte, un jeton d'accès (Access Token) ainsi qu'un jeton de rafraîchissement (Refresh Token) sont fournis en réponse dans un objet au format JSON.
;Prérequis
<syntaxhighlight lang="javascript">
Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php
{
 
  "token_type": "Bearer",
Le client de démonstration se présente de la manière suivante.
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}
</syntaxhighlight>


[[Fichier:Oauth2-client-demo.png]]
===Script client : Authorization Code===
Voici un exemple simple de client OAuth2 pour le mécanisme d'authentification par code d'autorisation (Authorization Code).


La démonstration est composée de deux colonnes. La première, nommée '''Authorization Code''' correspond [[#Authorization Code|au mécanisme d'autorisation du même nom]]. Elle dispose d'un bouton permettant de se connecter ainsi que d'une section indiquant les informations relatives à l'état de la connexion. La seconde colonne, nommée '''Client Credentials''' correspond elle aussi [[#Client Credentials|au mécanisme d'autorisation du même nom]]. Comme pour la première colonne, les éléments qui y sont présentés sont identiques. La différence étant que le bouton de connexion n'a pas le même effet étant donné que ces deux mécanismes sont différents. Chaque mécanisme est indépendant et il est possible de se connecter à un des deux mécanismes sans se connecter à l'autre ou se connecter aux deux en même temps.
Fichier de configuration ''config.authcode.json'' :
<syntaxhighlight lang="javascript">
{
  "client_id": "",
  "client_secret": "",
  "authorize_uri": "https://openflyers.com/nom-de-plateforme/oauth/authorize.php",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}
</syntaxhighlight>
Où <code>/path/to/client/</code> est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
*Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/ssl/AuthCodeDemo/</code>.
*Exemple sur un serveur Linux '''debian''': <code>./ssl/AuthCodeDemo/</code>.


Pour le mécanisme '''Authorization Code''', le bouton de connexion redirige le navigateur vers la page de connexion du logiciel OpenFlyers. Renseigner les identifiants de l'administrateur pour s'y connecter.
Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
* Nom d'utilisateur : '''admini'''.
* Mot de passe : '''azerty'''.


Une fois les informations saisies, la page suivante est affichée.
Script PHP :
<syntaxhighlight lang="php">
<?php
$localTokenFile    = 'token.json';
// create token file if it does not exist
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');


[[Fichier:Oauth authorize demo.png]]
$config                = json_decode(file_get_contents('config.authcode.json'), true);
$localToken            = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']     = $config;


Cliquer sur le bouton '''Autoriser l'application''' pour autoriser la connexion. Une fois le bouton cliqué, le navigateur est redirigé vers la page du client de démonstration OAuth2. La première colonne doit afficher l'état de connexion '''Connecté''' ainsi qu'un nouveau bouton.
// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$port    = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];


Pour le mécanisme '''Client Credentials''', le bouton de connexion, contrairement à celui du mécanisme '''Authorization Code''', ne redirige pas le navigateur vers la page de connexion du logiciel OpenFlyers. Le bouton de connexion utilise les identifiants du client, ici le couple clé privée/clé publique, pour initier la connexion avec le serveur d'autorisation et obtenir un jeton d'accès. Une fois la connexion établie, la seconde colonne doit afficher l'état de connexion '''Connecté''' ainsi qu'un nouveau bouton et un menu déroulant.
$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
$errorLog          = 'error_log.log';
$responseLog        = 'response_log.log';
$GLOBALS['baseURL'] = $baseURL;


Le client, une fois connecté sur les deux mécanismes, se présente de la manière suivante.
//Session cookies are used to store information necessary for the authorization code flow
session_start();


[[Fichier:Oauth2 connected demo.png]]
/**
* This function is used to make api calls to the Authorization Server and the Resource Server
*
* @param      $url
* @param      $post
* @param array $headers
*
* @return mixed
*/
function apiRequest($url, $post = null, $token = false, $auth = true, $refresh = false, $headers = array())
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);


Les deux mécanismes proposent deux types de ressources différentes. Le mécanisme '''Authorization Code''' propose de récupérer les informations de l'utilisateur connecté. Le mécanisme '''Client Credentials''' permet de récupérer les rapports génériques et personnalisés. Les ressources sont interchangeables et ne sont pas restreintes à un mécanisme particulier. Cependant, il est recommandé de suivre le schéma de la démonstration.
    $method = 'get';


Pour récupérer les informations de l'utilisateur connecté avec '''Authorization Code''', cliquer sur '''Récupérer les informations utilisateur'''.
    if ($post) {
        $method  = 'post';
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);


Pour récupérer un rapport avec '''Client Credentials''', sélectionner le rapport souhaité dans le menu déroulant et cliquer sur '''Récupérer le rapport'''.
        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
        $headersToSign['digest']      = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }


=Authentification TLS Mutuelle (mTLS)=
    $urlComponents = parse_url($url);


En général dans une communication TLS, seul le serveur a l'obligation de fournir un certificat. Il est également possible pour le client de fournir un certificat. Ce principe s'appelle l'authentification mutuelle et est mise en place avec Mutual TLS (ou [[Wikipedia-en:Mutual_authentication#mTLS|mTLS]]).
    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
    $headersToSign['Host']            = $urlComponents['host'];
    $headersToSign['Date']             = gmdate('D, j M Y H:i:s T');


OpenFlyers associe un certificat pour l'authentification mutuelle unique à chaque client OAuth2.
    // generating the signature header
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
    $privateKey          = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);


;Envoyer un certificat client
    $headers['Accept']     = 'application/json';
Côté client, le code suivant peut être utilisé pour fournir à cURL le certificat et la clé correspondante ainsi que le certificat du CA d'OpenFlyers à utiliser pour la connexion :
     $headers['User-Agent'] = $GLOBALS['baseURL'];
<php>curl_setopt_array($request, [
     unset($headersToSign['(request-target)']);
     CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
     $headers              += $headersToSign;
     CURLOPT_CAINFO    => $caCertificatePath,
     CURLOPT_SSLCERT    => $certificatePath,
     CURLOPT_SSLKEY    => $keyPath
]);</php>


'''À noter''' : le certificat du CA d'OpenFlyers est nécessaire pour assurer la validité des certificats utilisés.
    if ($token) {
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
    }


=HTTP Signature=
    // formatting the headers
    $httpFormattedHeaders = [];
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }


HTTP Signature utilise le principe de la signature numérique pour garantir l'authenticité et l'intégrité du message HTTP.
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);


La signature est générée à l'aide d'une clé privée et vérifiée à l'aide de la clé publique correspondante ou d'un certificat contenant cette clé publique.
    // for development environment only
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);


HTTP Signature utilise deux en-têtes HTTP :
    // defining TLS client certificates for Mutual TLS
*Signature : contient la signature et ses métadonnées.
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
*Digest : contient le corps du message haché.
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);


==Digest==
    $response = curl_exec($ch);
Le ''digest'' est calculé comme ceci : <code>digest = base64encode(sha256(corps du message))</code>


Et l'en-tête est structuré de la manière suivante : <code>Digest: SHA-256=<digest></code>
    // logging errors and responses
    errorLog(true, $response, $ch);


Un autre algorithme de hachage peut être utilisé, SHA-256 reste cependant le plus répendu.
    curl_close($ch);


;Exemple en php
    // in case an authentication request is executed
<php>$digestHeader = 'Digest: SHA-256=' . base64_encode(hash('sha256', $postData, true));</php>
    if ($auth) {
        // required when a refresh token request is issued
        // because there is only one header in the response
        // while there are two for regular auth requests
        if (!$refresh) {
            list($firstResponseHeaders, $secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 3);
            if (!$responseBody) {
                list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
            }
        } else {
            list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
        }


==Signature==
        $responseHeadersDigest = '';
L'en-tête HTTP de signature est structuré de la manière suivante : <code>Signature: keyId="<keyId>",algorithm="<algo>",headers="<signed_headers>",signature="<signature>"</code>
        $responseHeadersSignature = '';
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $secondResponseHeaders);
        $responseProtocol = array_shift($responseHeadersArray);


Le champ ''keyId'' correspond à un identifiant permettant l'identification de la clé utilisée pour vérifier la signature. Pour l'API d'OpenFlyers, sa valeur correspond à l'empreinte SHA-1 du certificat au format PEM à utiliser pour vérifier la signature.
        foreach ($responseHeadersArray as $value) {
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }


L'algorithme ''algo'' correspond à celui utilisé pour générer la signature, exemple : <code>rsa-sha256</code>.
        $responseDigestAlgo = '';
        $responseDigestKey = '';
        foreach ($responseHeaders as $key => $value) {
            if ($key === "digest") {
                $responseHeadersDigest = $value;
                // stripping SHA algorithm for later comparison
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
            } else if ($key === "signature")
                $responseHeadersSignature = $value;
        }


La valeur de ''signed_headers'' correspond à la liste des en-têtes inclus dans la signature séparés d'un espace. Exemple : <code>date digest (request-target)</code>
        // calculating response digest
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));


Pour générer la signature, une chaîne de caractères appelée <code>signing string</code> contenant les en-têtes au format <code>lowercase_header_name: value</code> séparés par une nouvelle ligne au format LF (<code>\n</code>) est d'abord générée. Exemple avec les en-têtes "date" et "(request-target)" :
        // checking digest validity
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }


<pre>(request-target): post /some/uri\ndate: Tue, 07 Jun 2014 20:51:35 GMT</pre>
        // extracting variables from signature header
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }


La signature est ensuite générée comme ceci : <code>base64encode(algo(signing string))</code>
        // generating siging string
        $signingStringArray = explode(" ", $signature['headers']);
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }


;Exemple en php
        // trimming last '\n' character
<php>function generateSignatureHeader(array $headersToSign, string $certificateFingerprint, string $privateKey): string
         $signingString = trim($signingString);
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
         $normalizedHeaderKey = trim(strtolower($key));
        $headers            .= $normalizedHeaderKey . ' ';
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }


    // trim extra whitespace
        // decoding signature
    $headers         = trim($headers);
         $decodedSignature = base64_decode($signature['signature']);
    $signatureString = trim($signatureString);


    // signing the signing string
        // verifying signature
    $signature = '';
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
    openssl_sign($signatureString, $signature, $privateKey, 'RSA-SHA256');
    $signature = base64_encode($signature);


    // compiling the header line
        if (!$signatureVerify) {
    return "Signature: keyId=\"$certificateFingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
            errorLog(false, false, "Signature is not correct");
}</php>
            while (($err = openssl_error_string()))
Les variables ''$certificateFingerprint'' et ''$privateKey'' correspondent respectivement à une empreinte SHA-1 de certificat et à une clé privée, tous deux au format PEM.
                errorLog(false, false, $err);
            die();
        }


La variable ''$headersToSign'' est un tableau formaté de la manière suivante :
        return json_decode($responseBody, true);
<php>[
     }
    $headerName => $headerValue,
     ...
]</php>


'''À noter''' : les en-têtes sont à signer côté client avec le certificat de signature signé par le CA d'OpenFlyers et dédié au client ainsi que la clé privée associée. Le certificat de signature dédié au client est téléchargeable depuis l'interface de gestion des clients OAuth2. Les en-têtes de la réponse du serveur quant à elles doivent être vérifiées avec le certificat de signature HTTP du serveur, téléchargeable aussi depuis l'interface de configuration des clients OAuth2.
    return $response;
}


=Client OAuth2=
/**
Une fois le client OAuth2 configuré sur OpenFlyers, il faut l'utiliser avec un client créé au préalable pour communiquer avec le serveur d'autorisation et l'API.
* This function logs curl responses and errors in text files
*
* @param mixed $response the response
* @param mixed $request  the request handle, used to get errors
*/
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;


Plusieurs [https://oauth.net/code/ bibliothèques] simplifiant la création d'un client sont disponibles.
    if ($response === false) {
        if ($curl)
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
        else
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}


Des scripts client basiques écrits en php sont aussi fournis pour les mécanismes [[#Script-client-:-authorization-code|''authorization_code'']] et [[#Script-client-:-client-credentials|''client_credentials'']]
/**
 
* This function generates the full signature header line from a set of headers, a certificate and the linked private key
==Authorization Code==
*
Ce flux OAuth2 se déroule en plusieurs étapes :
* @param array  $headersToSign the headers to sign, in the $key => $value format
*Le client redirige le navigateur de l'utilisateur vers l'URL d'autorisation.
* @param string $certificate  the certificate linked to the used private key
*Le navigateur de l'utilisateur est redirigé vers l'URL fourni durant la demande ou durant l'enregistrement du client.
* @param string $privateKey    the private key used to sign the headers
*Le client récupère un code d'autorisation grâce à la redirection précédente, et échange ce code contre un jeton d'accès auprès du serveur d'autorisation.
*
*Le client peut utiliser ce code d'accès :
* @return string the full signature header line
**Comme preuve d'authentification pour une solution SSO (Single Sign-On).
*/
**Pour accéder à des données sur le serveur distant en utilisant l'API.
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers            .= $normalizedHeaderKey . ' ';
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers        = trim($headers);
    $signatureString = trim($signatureString);


    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);
    // compiling the header line
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}
/**
* This function takes a code_verifier and outputs the corresponding code_challenge
*
* @param string $codeVerifier the generated code_verifier
*
* @return string the computed code_challenge
*/
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}


    +-----------+                  +------+                +-----------+                  +-------------+                  +-----------+
/**
    |Utilisateur|                  |Client|                |Navigateur |                  |  Serveur  |                  | Serveur |
  * This function takes an optional string length and outputs a random code_verifier string
    +-----+-----+                  +--+---+                +-----+-----+                  |Autorisation |                  | Ressources|
  *
          |                            |                          |                        +------+------+                  +-----+-----+
  * @param int $length the length of the output code_verifier. Default = 128
          |                            |                          |                              |                              |
*
          |                            |                  Demande|d'autorisation                |                              |
* @return string the code_verifier
          |                            +------------------------->+------------------------------>|                              |
* @throws Exception
          |                            |                          |                              |                              |
*/
          |                            |Authentification + Formulaire d'autorisation              |                              |
function generateCodeVerifier($length = 128): string
          |<---------------------------+--------------------------+<------------------------------+                              |
          |                            |                          |                              |                              |
          |                            |      Autorisation        |                              |                              |
          +----------------------------+------------------------->+------------------------------>|                              |
          |                            |                          |                              |                              |
          |                            |                      Code|d'autorisation                |                              |
          |                            |<-------------------------+<------------------------------+                              |
          |                            |                          |                              |                              |
          |                            |                Demande de|token                          |                              |
          |                            +--------------------------+------------------------------>|                              |
          |                            |                          |                              |                              |
          |                            |                Access (+|Refresh) token                |                              |
          |                            |<-------------------------+-------------------------------+                              |
          |                            |                          |                              |                              |
          |                            |                          |      Requête vers API        |                              |
          |                            +--------------------------+-------------------------------+------------------------------>|
          |                            |                          |                              |                              |
          |                            |                          |                              |                              |
          |                            |                          |                              |<------------------------------+
          |                            |                          |                              | Vérification d'autorisation  |
          |                            |                          |                              +------------------------------>|
          |                            |                          |                              |                              |
          |                            |                          |      Données/Réponse          |                              |
          |                            |<-------------------------+-------------------------------+-------------------------------+
          |                            |                          |                              |                              |
 
 
 
===Générer les codes pour PKCE===
Pour faire fonctionner le client OAuth2, il faut générer deux codes pour l'extension PKCE :
*Un ''code_verifier'' échangé pendant la demande de jeton d'accès.
*Un ''code_challenge'' dérivé du ''code_verifier'' et échangé pendant la demande d'autorisation.
 
;Générer le ''code_verifier''
<php>function generateCodeVerifier($length = 128): string
{
{
     $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
     $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
Line 419: Line 559:


     return $outputCode;
     return $outputCode;
}</php>
}


Cette fonction permet de générer un ''code_verifier'' avec une longueur donnée. Le ''code_verifier'' doit avoir une longueur entre 43 et 128 caractères.
/**
 
* This function calculates the entropy of a given string
;Générer le ''code_challenge''
*
<php>function computeCodeChallenge(string $codeVerifier): string
* @param string $string  the string for which to calculate the entropy
{
* @param string $charset a string with all the usable characters. Default = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
*
}</php>
* @return float|int
*/
function computeEntropy(string $string, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~')
{
    $chars = str_split($charset);
    $probs = [];


Cette fonction génère le ''code_challenge'' à partir du ''code_verifier'' fourni.
    foreach ($chars as $char) {
        $probs[$char] = floatval(substr_count($string, $char)) / strlen($string);
    }


    $sum = 0.0;
    foreach ($probs as $prob) {
        $sum += $prob != 0 ? $prob * log($prob, 2) : 0.0;
    }


===Demande d'autorisation===
    return -$sum;
Pour initier la demande d'autorisation, rediriger le navigateur de l'utilisateur vers l'URL : <code>GET https://openflyers.com/nom-de-plateforme/oauth/authorize.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
}


Envoyer également les paramètres suivants :
/**
{| class="wikitable"
* This function calculates the ideal (maximum) entropy for a string of a given length
!Nom!!Type!!Description
*
|-
* @param int $length length of the string. Default = 128
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
*
|-
* @return float|int
|response_type||string||Le type de réponse envoyée par le serveur. Doit utiliser la valeur "code" (sans guillements) pour ce mécanisme.
*/
|-
function idealEntropy(int $length = 128)
|redirect_uri||string||L'URI (ou l'URL) fourni pendant l'enregistrement du client et vers lequel l'utilisateur est redirigé après la demande d'autorisation.
{
|-
    $prob = 1.0 / $length;
|scope||string||[[#Liste-des-scopes-disponibles|Liste des droits demandés par le client]], séparés par des espaces.
|-
|state||string||Chaîne de caractère aléatoire utilisée pour éviter les [https://fr.wikipedia.org/wiki/Cross-site_request_forgery attaques CSRF]. '''Fortement recommandé'''.
|-
|code_challenge||string||Code nécessaire pour le fonctionnement de l'extension [[#Générer-les-codes-pour-PKCE|PKCE]].
|-
|code_challenge_method||string||Méthode utilisée pour générer le ''code_challenge''. Ici, la valeur est "S256".
|}


===Demande de jeton d'accès===
    return -1.0 * $length * $prob * log($prob, 2);
Après avoir répondu à la demande, l'utilisateur est redirigé vers l'URI fourni pendant l'enregistrement du client. Si la demande est acceptée, un code temporaire : ''code'' est fourni, ainsi que le paramètre ''state'' fourni pendant la demande avec la même valeur. Si la demande est refusée, un code d'erreur est renvoyé.
}
;Si le paramètre ''state'' a une valeur différente de celle envoyée avec la demande, c'est peut-être une tentative d'attaque et il faut refuser la réponse.


Echanger ce ''code'' contre un jeton d'accès via l'URL : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
/**
* This function is used to initiate the authentication code flow.
*
* @param string $clientID    the client's ID
* @param string $redirectURL  the URL where to redirect after auth
* @param string $authorizeURL the target URL to request authorization
*
* @throws Exception
*/
function login(string $clientID, string $redirectURL, string $authorizeURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');


Les paramètres suivants sont également nécessaires :
    // This unguessable string is used to prevent csrf attacks
{| class="wikitable"
    $_SESSION['state'] = bin2hex(random_bytes(16));
!Nom!!Type!!Description
 
|-
    // Generate code_verifier and code_challenge for PKCE   
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
    $_SESSION['code_verifier']  = generateCodeVerifier();
|-
    $_SESSION['code_challenge'] = computeCodeChallenge($_SESSION['code_verifier']);
|client_secret||string||La passphrase reçue pendant l'enregistrement du client.
 
|-
    // required parameters for the redirection, redirect_uri is where the browser should be redirected
|code||string||Le code temporaire reçu dans la réponse à la demande d'autorisation.
    // when the user grants (or denies) access, scope are the authorizations (rights) requested
|-
    $params = [
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, sa valeur doit être ''authorization_code''
        'response_type'        => 'code',
|-
        'client_id'             => $clientID,
|redirect_uri||string||L'URI (ou l'URL) de redirection fourni pendant l'enregistrement du client.
        'redirect_uri'         => $redirectURL,
|-
        'scope'                 => 'default.login',
|code_verifier||string||Le ''code_verifier'' utilisé pour générer le ''code_challenge'' de la demande d'autorisation.
        'state'                 => $_SESSION['state'],
|}
        'code_challenge'       => $_SESSION['code_challenge'],
        'code_challenge_method' => 'S256'
    ];


Si la requête est correcte, un jeton d'accès (Access Token) ainsi qu'un jeton de rafraîchissement (Refresh Token) sont fournis en réponse dans un objet au format JSON.
    // redirecting the browser to the AS authorization endpoint to obtain the authorization code
<javascript>{
    header('Location: ' . $authorizeURL . '?' . http_build_query($params));
  "token_type": "Bearer",
}
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}</javascript>


===Script client : Authorization Code===
/**
Voici un exemple simple de client OAuth2 pour le mécanisme d'authentification par code d'autorisation (Authorization Code).
* This function deletes the access token from the session
*
* @param string $baseURL
*/
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');


Fichier de configuration ''config.authcode.json'' :
    header('Location: ' . $baseURL);
<javascript>{
}
  "client_id": "",
  "client_secret": "",
  "authorize_uri": "https://openflyers.com/nom-de-plateforme/oauth/authorize.php",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}</javascript>
Où <code>/path/to/client/</code> est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
*Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/ssl/AuthCodeDemo/</code>.
*Exemple sur un serveur Linux '''debian''': <code>./ssl/AuthCodeDemo/</code>.


Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
function displayMenuLoggedIn(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';


Script PHP :
    echo '<form id="view_repos" method="post">';
<php><?php
    echo '<input type="hidden" id="action" name="action" value="view">';
$localTokenFile    = 'token.json';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
// create token file if it does not exist
     echo '</form>';
if (!file_exists($localTokenFile))
     file_put_contents($localTokenFile, '');


$config                = json_decode(file_get_contents('config.authcode.json'), true);
    echo '<form id="logout" method="post">';
$localToken            = json_decode(file_get_contents($localTokenFile), true);
    echo '<input type="hidden" id="action" name="action" value="logout">';
$GLOBALS['config']      = $config;
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}


// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
function displayMenuLoggedOut(): void
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
{
$port     = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    echo '<h2><a href="/">Home</a></h2>';
     || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];
     echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
     echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}


$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
// display client "home" page
$errorLog          = 'error_log.log';
if (!isset($_POST['action'])) {
$responseLog        = 'response_log.log';
    if (!empty($localToken['access_token'])) {
$GLOBALS['baseURL'] = $baseURL;
        displayMenuLoggedIn();
    } else {
        displayMenuLoggedOut();
    }
}


//Session cookies are used to store information necessary for the authorization code flow
if (isset($_POST['action'])) {
session_start();
    // Building query array for resource dumping
 
    $repoQueryParameters = [
/**
        'resource_type' => 'user_information',
* This function is used to make api calls to the Authorization Server and the Resource Server
        'client_id' => $config['client_id']
*
    ];
* @param      $url
    switch ($_POST['action']) {
* @param      $post
        case 'login':
* @param array $headers
            login($config['client_id'], $baseURL, $config['authorize_uri']);
*
            break;
* @return mixed
        case 'logout':
*/
            logout($baseURL);
function apiRequest($url, $post = null, $token = false, $auth = true, $refresh = false, $headers = array())
            break;
{
        case 'view':
    $ch = curl_init($url);
            // call to the resource server to retreive user information
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $resources = apiRequest(
    curl_setopt($ch, CURLOPT_HEADER, true);
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );


    $method = 'get';
            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);


    if ($post) {
            $resourceHeaders = explode("\r\n", $resourceHeader);
        $method  = 'post';
            $firstHeaderLine = array_shift($resourceHeaders);
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);


        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
            // Displaying user information
        $headersToSign['digest']      = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
            echo '<pre>';
    }
            echo $resourceBody;
            echo '</pre>';


    $urlComponents = parse_url($url);
            // Automatic refreshing token once access token is expired
 
            if (strpos($firstHeaderLine, "401")) {
    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
                $errorBody = json_decode($resourceBody, true);
    $headersToSign['Host']             = $urlComponents['host'];
                if ($errorBody['error'] == 'access_denied' && isset($errorBody['hint'])) {
     $headersToSign['Date']             = gmdate('D, j M Y H:i:s T');
                    if ($errorBody['hint'] == 'Access token could not be verified') {
                        $token = apiRequest(
                            $config['token_uri'],
                            [
                                'grant_type'    => 'refresh_token',
                                'refresh_token' => $localToken['refresh_token'],
                                'client_id'     => $config['client_id'],
                                'client_secret' => empty($config['client_secret']) ? null : $config['client_secret']
                            ],
                            false,
                            true,
                            true
                        );


    // generating the signature header
                        // We store the access token in the session so the user is "connected"
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
                        // Only if the request has been successfully executed
    $privateKey          = file_get_contents($GLOBALS['config']['sign_key']);
                        if (isset($token['access_token'])) {
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);
                            file_put_contents($localTokenFile, json_encode($token, true));


    $headers['Accept']    = 'application/json';
                            // Redirecting the user's browser to the "home" page
    $headers['User-Agent'] = $GLOBALS['baseURL'];
                            header('Location: ' . $baseURL);
    unset($headersToSign['(request-target)']);
                        }
    $headers              += $headersToSign;
                    }
 
                }
    if ($token) {
            }
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
            break;
     }
     }
}


    // formatting the headers
// Once the browser has been redirected to the AS to ask for the user's authorization, assuming it has been granted,
    $httpFormattedHeaders = [];
// the browser will get back here with a "code" parameter in the query string
     foreach ($headers as $key => $value) {
if (isset($_GET['code'])) {
         $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    // The AS MUST redirect the browser here with the exact same state parameter we sent it before, so we check if it is indeed the same
    // to detect if the oauth flow has been tampered with
     if (!isset($_GET['state']) || $_SESSION['state'] != $_GET['state']) {
         header('Location: ' . $baseURL . '?error=invalid_state');
        die();
     }
     }


     curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
     // We communicate directly with the AS to exchange the code received against an access token.
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);
    // The id/secret pair is send to authenticate the client, the redirect_uri is sent to verify the code's validity
    $token = apiRequest(
        $config['token_uri'],
        [
            'grant_type'    => 'authorization_code',
            'client_id'    => $config['client_id'],
            'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
            'redirect_uri'  => $baseURL,
            'code'          => $_GET['code'],
            'code_verifier' => $_SESSION['code_verifier']
        ]
    );


     // for development environment only
     // We store the access token in the session so the user is "connected"
     //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
     // Only if the request has been successfully executed
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    if (isset($token['access_token'])) {
    //curl_setopt($ch, CURLOPT_VERBOSE, true);
        file_put_contents($localTokenFile, json_encode($token, true));


    // defining TLS client certificates for Mutual TLS
        // Redirecting the user's browser to the "home" page
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
        header('Location: ' . $baseURL);
     curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
     } else {
     curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
        echo $token;
     curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);
     }
     die();
}
</syntaxhighlight>


    $response = curl_exec($ch);
;Ce script a été conçu pour montrer le fonctionnement du mécanisme afin de tester l'implémentation d'un serveur et n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.


     // logging errors and responses
==Client Credentials==
    errorLog(true, $response, $ch);
Ce mécanisme d'autorisation est adapté pour l'automatisation. Il fonctionne de la manière suivante :
*Le client effectue la demande de jeton d'accès au serveur d'autorisation en fournissant ses identifiants.
*Le serveur authentifie le client avec les identifiants fournis et renvoie un jeton d'accès.
*Le client utilise ce jeton d'accès pour accéder à des données via l'API.
 
 
    +------+                      +-------------+                  +-----------+
    |Client|                      |  Serveur  |                  |  Serveur  |
     +--+---+                      |Autorisation |                  | Ressources|
      |                          +------+------+                  +-----+-----+
      |        Authentification          |                              |
      |        + Demande token          |                              |
      +--------------------------------->|                              |
      |                                  |                              |
      |          Access token            |                              |
      |<---------------------------------+                              |
      |                                  |                              |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                              |
      |                                  |                              |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                              |
      |                        Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                              |


    curl_close($ch);


    // in case an authentication request is executed
    if ($auth) {
        // required when a refresh token request is issued
        // because there is only one header in the response
        // while there are two for regular auth requests
        if (!$refresh) {
            list($firstResponseHeaders, $secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 3);
            if (!$responseBody) {
                list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
            }
        } else {
            list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
        }


        $responseHeadersDigest = '';
===Demande de jeton d'accès===
        $responseHeadersSignature = '';
Pour obtenir un jeton d'accès, il faut effectuer la requête suivante : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
        // extracting digest and signature from headers
 
        $responseHeadersArray = explode("\r\n", $secondResponseHeaders);
Les paramètres suivants sont nécessaires :
        $responseProtocol = array_shift($responseHeadersArray);
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|client_secret||string||La passphrase reçue pendant l'enregistrement du client.
|-
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, la valeur doit être ''client_credentials''.
|-
|scope||string||[[#Liste-des-scopes-disponibles|Liste des droits demandés par le client]], séparé par des espaces.
|}


        foreach ($responseHeadersArray as $value) {
;Ce mécanisme a la particularité de ne pas nécessiter d'URI de redirection, il n'est donc pas utile d'en renseigner un lors de l'enregistrement d'un client.
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }


        $responseDigestAlgo = '';
Si la requête est correcte, un jeton d'accès (Access Token) est fourni en réponse dans un objet au format JSON.
        $responseDigestKey = '';
<syntaxhighlight lang="javascript">
        foreach ($responseHeaders as $key => $value) {
{
            if ($key === "digest") {
  "token_type": "Bearer",
                $responseHeadersDigest = $value;
  "expires_in": 3600,
                // stripping SHA algorithm for later comparison
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw"
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
}
            } else if ($key === "signature")
</syntaxhighlight>
                $responseHeadersSignature = $value;
        }


        // calculating response digest
===Script client : Client Credentials===
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
Voici un exemple simple d'un client OAuth2 pour le mécanisme d'authentification par les identifiants client (Client Credentials).


        // checking digest validity
Fichier de configuration ''config.clientcred.json'' :
        if ($responseDigest !== $responseDigestKey) {
<syntaxhighlight lang="javascript">
            errorLog(false, false, "Digests are not the same");
{
            die();
  "client_id": "",
        }
  "client_secret": "",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/nom-de-plateforme/oauth/revoke.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}
</syntaxhighlight>
Où <code>/path/to/client/</code> est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
*Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/ssl/ClientCredDemo/</code>.
*Exemple sur un serveur Linux '''debian''': <code>./ssl/ClientCredDemo/</code>.


        // extracting variables from signature header
Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }


        // generating siging string
Script php :
        $signingStringArray = explode(" ", $signature['headers']);
<syntaxhighlight lang="php">
        $signingString = '';
<?php
        foreach ($signingStringArray as $value) {
$localTokenFile    = 'token.json';
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
// create token file if it does not exist
        }
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');


        // trimming last '\n' character
$config                = json_decode(file_get_contents('config.clientcred.json'), true);
        $signingString = trim($signingString);
$localToken            = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']      = $config;


        // decoding signature
// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
        $decodedSignature = base64_decode($signature['signature']);
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$port    = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];


        // verifying signature
$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
$errorLog          = 'error_log.log';
$responseLog        = 'response_log.log';
$GLOBALS['baseURL'] = $baseURL;


        if (!$signatureVerify) {
$replacementListItems = [
            errorLog(false, false, "Signature is not correct");
    1 => "year",
            while (($err = openssl_error_string()))
    2 => "validityTypeId",
                errorLog(false, false, $err);
    3 => "icao",
            die();
    4 => "profileId",
        }
    7 => "accountingId",
    8 => "paymentType",
    9 => "startDate",
    10 => "endDate",
    11 => "occupiedSeat",
    12 => "date",
    13 => "activityTypeId",
    14 => "age",
    15 => "resourceId",
    16 => "personId",
    17 => "accountId",
    20 => "rightPlacePersonId",
    21 => "month",
    22 => "numberMonth",
    23 => "oneValidityTypeId",
];


        return json_decode($responseBody, true);
//Session cookies are used to store information necessary for the authorization code flow
    }
session_start();
 
    return $response;
}


/**
/**
  * This function logs curl responses and errors in text files
  * This function is used to make api calls to the RS
*
* @param      $url
* @param      $post
* @param array $headers
  *
  *
  * @param mixed $response the response
  * @return mixed
* @param mixed $request  the request handle, used to get errors
  */
  */
function errorLog($curl, $response, $request = false)
function apiRequest($url, $post = null, $token = false, $auth = true, $headers = array())
{
{
     $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
     $ch = curl_init($url);
     global $errorLog;
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
     global $responseLog;
     curl_setopt($ch, CURLOPT_HEADER, true);


     if ($response === false) {
     $method = 'get';
        if ($curl)
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
        else
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}


/**
    if ($post) {
* This function generates the full signature header line from a set of headers, a certificate and the linked private key
        $method  = 'post';
*
        $postData = http_build_query($post);
* @param array  $headersToSign the headers to sign, in the $key => $value format
        curl_setopt($ch, CURLOPT_POST, true);
* @param string $certificate  the certificate linked to the used private key
         curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
* @param string $privateKey    the private key used to sign the headers
 
*
         $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
* @return string the full signature header line
         $headersToSign['digest']      = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
*/
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
         $normalizedHeaderKey = trim(strtolower($key));
         $headers            .= $normalizedHeaderKey . ' ';
         $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
     }
     }
    $headers        = trim($headers);
    $signatureString = trim($signatureString);


    // signing the signing string
     $urlComponents = parse_url($url);
     $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);


     // compiling the header line
     $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
    $headersToSign['Host']            = $urlComponents['host'];
}
    $headersToSign['Date']            = gmdate('D, j M Y H:i:s T');


/**
    // generating the signature header
* This function takes a code_verifier and outputs the corresponding code_challenge
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
*
     $privateKey          = file_get_contents($GLOBALS['config']['sign_key']);
* @param string $codeVerifier the generated code_verifier
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);
*
* @return string the computed code_challenge
*/
function computeCodeChallenge(string $codeVerifier): string
{
     return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}


/**
    $headers['Accept']    = 'application/json';
* This function takes an optional string length and outputs a random code_verifier string
    $headers['User-Agent'] = $GLOBALS['baseURL'];
*
     unset($headersToSign['(request-target)']);
* @param int $length the length of the output code_verifier. Default = 128
     $headers              += $headersToSign;
*
* @return string the code_verifier
* @throws Exception
*/
function generateCodeVerifier($length = 128): string
{
     $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
     $outputCode = '';


     for ($i = 0; $i < $length; $i++) {
     if ($token) {
         $index      = random_int(0, strlen($characters) - 1);
         $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
        $outputCode .= $characters[$index];
     }
     }


     return $outputCode;
     // formatting the headers
}
    $httpFormattedHeaders = [];
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }


/**
    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
* This function calculates the entropy of a given string
     curl_setopt($ch, CURLINFO_HEADER_OUT, true);
*
* @param string $string  the string for which to calculate the entropy
* @param string $charset a string with all the usable characters. Default = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~
*
* @return float|int
*/
function computeEntropy(string $string, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~')
{
     $chars = str_split($charset);
    $probs = [];


     foreach ($chars as $char) {
     // for development environment only
        $probs[$char] = floatval(substr_count($string, $char)) / strlen($string);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    }
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);


     $sum = 0.0;
     // defining TLS client certificates for Mutual TLS
     foreach ($probs as $prob) {
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
        $sum += $prob != 0 ? $prob * log($prob, 2) : 0.0;
     curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    }
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);


     return -$sum;
     $response = curl_exec($ch);
}


/**
    // logging errors and responses
* This function calculates the ideal (maximum) entropy for a string of a given length
    errorLog(true, $response, $ch);
*
* @param int $length length of the string. Default = 128
*
* @return float|int
*/
function idealEntropy(int $length = 128)
{
    $prob = 1.0 / $length;


     return -1.0 * $length * $prob * log($prob, 2);
     curl_close($ch);
}


/**
    // in case an authentication request is executed
* This function is used to initiate the authentication code flow.
     if ($auth) {
*
        list($responseHeader, $responseBody) = explode("\r\n\r\n", $response, 2);
* @param string $clientID     the client's ID
* @param string $redirectURL  the URL where to redirect after auth
* @param string $authorizeURL the target URL to request authorization
*
* @throws Exception
*/
function login(string $clientID, string $redirectURL, string $authorizeURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');


    // This unguessable string is used to prevent csrf attacks
        $responseHeadersDigest = '';
    $_SESSION['state'] = bin2hex(random_bytes(16));
        $responseHeadersSignature = '';
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $responseHeader);
        $responseProtocol = array_shift($responseHeadersArray);


    // Generate code_verifier and code_challenge for PKCE   
        foreach ($responseHeadersArray as $value) {
    $_SESSION['code_verifier']  = generateCodeVerifier();
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
    $_SESSION['code_challenge'] = computeCodeChallenge($_SESSION['code_verifier']);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }


    // required parameters for the redirection, redirect_uri is where the browser should be redirected
        $responseDigestAlgo = '';
    // when the user grants (or denies) access, scope are the authorizations (rights) requested
         $responseDigestKey = '';
    $params = [
         foreach ($responseHeaders as $key => $value) {
        'response_type'        => 'code',
            if ($key === "digest") {
         'client_id'            => $clientID,
                $responseHeadersDigest = $value;
        'redirect_uri'          => $redirectURL,
                 // stripping SHA algorithm for later comparison
        'scope'                 => 'default.login',
                 list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
        'state'                 => $_SESSION['state'],
            } else if ($key === "signature")
        'code_challenge'        => $_SESSION['code_challenge'],
                $responseHeadersSignature = $value;
        'code_challenge_method' => 'S256'
        }
    ];


    // redirecting the browser to the AS authorization endpoint to obtain the authorization code
        // calculating response digest
    header('Location: ' . $authorizeURL . '?' . http_build_query($params));
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));
}


/**
        // checking digest validity
* This function deletes the access token from the session
        if ($responseDigest !== $responseDigestKey) {
*
            errorLog(false, false, "Digests are not the same");
* @param string $baseURL
            die();
*/
        }
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');


    header('Location: ' . $baseURL);
        // extracting variables from signature header
}
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }


function displayMenuLoggedIn(): void
        // generating siging string
{
        $signingStringArray = explode(" ", $signature['headers']);
    echo '<h2><a href="/">Home</a></h2>';
        $signingString = '';
    echo '<h3>Logged In</h3>';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }


    echo '<form id="view_repos" method="post">';
        // trimming last '\n' character
    echo '<input type="hidden" id="action" name="action" value="view">';
        $signingString = trim($signingString);
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';


    echo '<form id="logout" method="post">';
        // decoding signature
    echo '<input type="hidden" id="action" name="action" value="logout">';
        $decodedSignature = base64_decode($signature['signature']);
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}


function displayMenuLoggedOut(): void
        // verifying signature
{
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}


// display client "home" page
        if (!$signatureVerify) {
if (!isset($_POST['action'])) {
            errorLog(false, false, "Signature is not correct");
    if (!empty($localToken['access_token'])) {
            while (($err = openssl_error_string()))
        displayMenuLoggedIn();
                errorLog(false, false, $err);
    } else {
            die();
         displayMenuLoggedOut();
        }
 
         return json_decode($responseBody, true);
     }
     }
    return $response;
}
}


if (isset($_POST['action'])) {
/**
    // Building query array for resource dumping
* This function logs curl responses and errors in text files
    $repoQueryParameters = [
*
        'resource_type' => 'user_information',
* @param mixed $response the response
        'client_id' => $config['client_id']
* @param mixed $request  the request handle, used to get errors
    ];
*/
    switch ($_POST['action']) {
function errorLog($curl, $response, $request = false)
        case 'login':
{
            login($config['client_id'], $baseURL, $config['authorize_uri']);
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
            break;
    global $errorLog;
        case 'logout':
    global $responseLog;
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server to retreive user information
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );


            // Separating headers from body
    if ($response === false) {
             list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
        if ($curl)
 
             file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
             $resourceHeaders = explode("\r\n", $resourceHeader);
        else
            $firstHeaderLine = array_shift($resourceHeaders);
             file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}


            // Displaying user information
/**
            echo '<pre>';
* This function generates the full signature header line from a set of headers, a certificate and the linked private key
            echo $resourceBody;
*
            echo '</pre>';
* @param array  $headersToSign the headers to sign, in the $key => $value format
 
* @param string $certificate  the certificate linked to the used private key
            // Automatic refreshing token once access token is expired
* @param string $privateKey    the private key used to sign the headers
            if (strpos($firstHeaderLine, "401")) {
*
                $errorBody = json_decode($resourceBody, true);
* @return string the full signature header line
                if ($errorBody['error'] == 'access_denied' && isset($errorBody['hint'])) {
*/
                    if ($errorBody['hint'] == 'Access token could not be verified') {
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
                        $token = apiRequest(
{
                            $config['token_uri'],
    // generating the signing string and header list
                            [
    $headers        = '';
                                'grant_type'    => 'refresh_token',
    $signatureString = '';
                                'refresh_token' => $localToken['refresh_token'],
    foreach ($headersToSign as $key => $value) {
                                'client_id'     => $config['client_id'],
        $normalizedHeaderKey = trim(strtolower($key));
                                'client_secret' => empty($config['client_secret']) ? null : $config['client_secret']
        $headers            .= $normalizedHeaderKey . ' ';
                            ],
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
                            false,
    }
                            true,
     $headers        = trim($headers);
                            true
    $signatureString = trim($signatureString);
                        );


                        // We store the access token in the session so the user is "connected"
    // signing the signing string
                        // Only if the request has been successfully executed
    $signature = '';
                        if (isset($token['access_token'])) {
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
                            file_put_contents($localTokenFile, json_encode($token, true));
    $signature = base64_encode($signature);


                            // Redirecting the user's browser to the "home" page
    // compiling the header line
                            header('Location: ' . $baseURL);
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
                        }
                    }
                }
            }
            break;
    }
}
}


// Once the browser has been redirected to the AS to ask for the user's authorization, assuming it has been granted,
/**
// the browser will get back here with a "code" parameter in the query string
* This function deletes the access token from the session
if (isset($_GET['code'])) {
*
     // The AS MUST redirect the browser here with the exact same state parameter we sent it before, so we check if it is indeed the same
* @param string $baseURL
    // to detect if the oauth flow has been tampered with
*/
     if (!isset($_GET['state']) || $_SESSION['state'] != $_GET['state']) {
function logout(string $baseURL): void
        header('Location: ' . $baseURL . '?error=invalid_state');
{
        die();
     global $localTokenFile;
    }
     file_put_contents($localTokenFile, '');
 
    header('Location: ' . $baseURL);
}


    // We communicate directly with the AS to exchange the code received against an access token.
function displayMenuLoggedIn(): void
    // The id/secret pair is send to authenticate the client, the redirect_uri is sent to verify the code's validity
{
     $token = apiRequest(
     global $replacementListItems;
        $config['token_uri'],
    echo '<h2><a href="/">Home</a></h2>';
        [
    echo '<h3>Logged In</h3>';
            'grant_type'    => 'authorization_code',
            'client_id'    => $config['client_id'],
            'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
            'redirect_uri' => $baseURL,
            'code'          => $_GET['code'],
            'code_verifier' => $_SESSION['code_verifier']
        ]
    );


     // We store the access token in the session so the user is "connected"
     echo '<form id="view_repos" method="post">';
     // Only if the request has been successfully executed
     echo '<label for="report_id">Report ID: </label>';
     if (isset($token['access_token'])) {
     echo '<input type="number" id="report_id" name="report_id"><br><br>';
        file_put_contents($localTokenFile, json_encode($token, true));
    foreach ($replacementListItems as $value) {
 
         echo '<label for="' . $value . '">' . $value . ': </label>';
         // Redirecting the user's browser to the "home" page
         echo '<input type="text" id="' . $value . '" name="' . $value . '"><br><br>';
        header('Location: ' . $baseURL);
    } else {
         echo $token;
     }
     }
     die();
     echo '<input type="hidden" id="action" name="action" value="view">';
}</php>
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';


;Ce script a été conçu pour montrer le fonctionnement du mécanisme afin de tester l'implémentation d'un serveur et n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.
    echo '<form id="logout" method="post">';
    echo '<input type="hidden" id="action" name="action" value="logout">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}


==Client Credentials==
function displayMenuLoggedOut(): void
Ce mécanisme d'autorisation est adapté pour l'automatisation. Il fonctionne de la manière suivante :
{
*Le client effectue la demande de jeton d'accès au serveur d'autorisation en fournissant ses identifiants.
    echo '<h2><a href="/">Home</a></h2>';
*Le serveur authentifie le client avec les identifiants fournis et renvoie un jeton d'accès.
    echo '<h3>Not logged in</h3>';
*Le client utilise ce jeton d'accès pour accéder à des données via l'API.
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}


// display client "home" page
if (!isset($_POST['action'])) {
    if (!empty($localToken['access_token'])) {
        displayMenuLoggedIn();
    } else {
        displayMenuLoggedOut();
    }
}


     +------+                      +-------------+                  +-----------+
if (isset($_POST['action'])) {
     |Client|                      |  Serveur  |                  |  Serveur  |
    $replacementList = [];
     +--+---+                      |Autorisation |                  | Ressources|
     // Building replacement list
      |                          +------+------+                  +-----+-----+
    foreach ($replacementListItems as $key => $value) {
      |        Authentification          |                              |
        $replacementList[$value] = (isset($_POST[$value]) && strlen($_POST[$value]) > 0) ? $_POST[$value] : null;
      |         + Demande token          |                              |
     }
      +--------------------------------->|                              |
     // Building query array
      |                                  |                              |
    $repoQueryParameters = [
      |          Access token            |                              |
        'resource_type' => 'generic_report',
      |<---------------------------------+                              |
         'client_id' => $config['client_id'],
      |                                  |                              |
        'report_id' => (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0) ? $_POST['report_id'] : null,
      |                      Requête vers|API                            |
        'replacementList' => $replacementList
      +----------------------------------+------------------------------>|
    ];
      |                                  |                              |
    switch ($_POST['action']) {
      |                                  |                              |
        case 'login':
      |                                  |<------------------------------+
            $token = apiRequest(
      |                                  |  Vérification d'autorisation  |
                $config['token_uri'],
      |                                  +------------------------------>|
                [
      |                                  |                              |
                    'grant_type'    => 'client_credentials',
      |                        Données /|Réponse                        |
                    'client_id'    => $config['client_id'],
      |<---------------------------------+-------------------------------+
                    'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
      |                                  |                              |
                    'scope'        => 'genericreports.readonly reports.readonly'
                ]
            );
            // We store the access token in the session so the user is "connected"
            // Only if the request has been successfully executed
            if (isset($token['access_token'])) {
                file_put_contents($localTokenFile, json_encode($token, true));


                // Redirecting the user's browser to the "home" page
                header('Location: ' . $baseURL);
            } else {
                echo $token;
            }
            break;
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );


            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);


===Demande de jeton d'accès===
            $resourceHeaders = explode("\r\n", $resourceHeader);
Pour obtenir un jeton d'accès, il faut effectuer la requête suivante : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
            $firstHeaderLine = array_shift($resourceHeaders);
 
Les paramètres suivants sont nécessaires :
{| class="wikitable"
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|client_secret||string||La passphrase reçue pendant l'enregistrement du client.
|-
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, la valeur doit être ''client_credentials''.
|-
|scope||string||[[#Liste-des-scopes-disponibles|Liste des droits demandés par le client]], séparé par des espaces.
|}
 
;Ce mécanisme a la particularité de ne pas nécessiter d'URI de redirection, il n'est donc pas utile d'en renseigner un lors de l'enregistrement d'un client.
 
Si la requête est correcte, un jeton d'accès (Access Token) est fourni en réponse dans un objet au format JSON.
<javascript>{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw"
}</javascript>


===Script client : Client Credentials===
            // Building form for download CSV button
Voici un exemple simple d'un client OAuth2 pour le mécanisme d'authentification par les identifiants client (Client Credentials).
            echo '<form method="post">';
            foreach ($_POST as $key => $value) {
                if ($key == "action") {
                    $value = "download";
                }
                echo '<input type="hidden" id="' . $key . '" name="' . $key . '" value="' . $value . '">';
            }
            if (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0 && !isset(json_decode($resourceBody, true)['error'])) {
                echo '<button>Download as CSV</button>';
            }
            echo '</form>';


Fichier de configuration ''config.clientcred.json'' :
            // Displaying requested content
<javascript>{
            echo '<pre>';
  "client_id": "",
            echo (strpos($firstHeaderLine, "200")) ? json_decode($resourceBody) : $resourceBody;
  "client_secret": "",
            echo '</pre>';
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
            break;
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
        case 'download':
  "revoke_uri": "https://openflyers.com/nom-de-plateforme/oauth/revoke.php",
            // call to the resource server
  "auth_cert": "/path/to/client/auth_cert.crt",
            $resources = apiRequest(
  "auth_key": "/path/to/client/auth.key",
                $config['resource_uri'],
  "sign_cert": "/path/to/client/sign_cert.crt",
                $repoQueryParameters,
  "sign_key": "/path/to/client/sign.key",
                $localToken,
  "auth_cacert": "/path/to/ca.crt",
                false
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
            );
}</javascript>
Où <code>/path/to/client/</code> est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
*Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/ssl/ClientCredDemo/</code>.
*Exemple sur un serveur Linux '''debian''': <code>./ssl/ClientCredDemo/</code>.


Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.
            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
            $resourceHeaders = explode("\r\n", $resourceHeader);
            array_shift($resourceHeaders);


Script php :
            // Dumping headers to insert them in current page
<php><?php
            foreach ($resourceHeaders as $key => $value) {
$localTokenFile    = 'token.json';
                header($value);
// create token file if it does not exist
            }
if (!file_exists($localTokenFile))
            echo json_decode($resourceBody);
     file_put_contents($localTokenFile, '');
            break;
     }
}
</syntaxhighlight>


$config                = json_decode(file_get_contents('config.clientcred.json'), true);
;Ce script a été conçu pour montrer le fonctionnement du mécanisme et tester l'implémentation d'un serveur, il n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.
$localToken            = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']      = $config;


// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
==Refresh Token==
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
Refresh Token (ou jeton de rafraîchissement en français) est un mécanisme d'autorisation particulier. Il ne peut fonctionner en tant que tel, il fonctionne de pair avec [[#Authorization-Code|Authorization Code]]. Lorsqu'un jeton d'accès arrive est arrivé en fin de vie, si le client y est autorisé, il peut faire une demande de renouvellement de son jeton d'accès auprès du serveur d'autorisation en présentant son jeton de rafraîchissement. Le serveur d'autorisation vérifie la validité et génère un autre jeton d'accès qu'il transmet au client, sans que l'utilisateur final n'aît à se connecter de nouveau.
$port    = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];


$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
Le principe de fonctionnement du rafraîchissement d'un jeton est le suivant :
$errorLog          = 'error_log.log';
* Le jeton d'accès du client est arrivé à péremption. Le client effectue une demande de renouvellement en transmettant son Refresh Token au serveur d'autorisation.
$responseLog        = 'response_log.log';
* Le serveur d'autorisation vérifie la validité des informations, "consomme" le jeton de rafraîchissement et génère une nouvelle paire de jetons
$GLOBALS['baseURL'] = $baseURL;
* Le client reçoit sa nouvelle paire et utilise le nouveau jeton d'accès pour accéder aux ressources


$replacementListItems = [
    +------+                      +-------------+                  +-----------+
     1 => "year",
     |Client|                      |  Serveur  |                  |  Serveur  |
     2 => "validityTypeId",
     +--+---+                      |Autorisation |                  | Ressources|
    3 => "icao",
      |                          +------+------+                  +-----+-----+
    4 => "profileId",
      |        Authentification          |                              |
    7 => "accountingId",
      |        + Refresh Token          |                              |
    8 => "paymentType",
      +--------------------------------->|                              |
    9 => "startDate",
      |                                  |                              |
    10 => "endDate",
      |    Access (+ Refresh) token      |                              |
    11 => "occupiedSeat",
      |<---------------------------------+                              |
    12 => "date",
      |                                  |                              |
    13 => "activityTypeId",
      |                      Requête vers|API                            |
    14 => "age",
      +----------------------------------+------------------------------>|
    15 => "resourceId",
      |                                  |                              |
    16 => "personId",
      |                                  |                              |
    17 => "accountId",
      |                                  |<------------------------------+
    20 => "rightPlacePersonId",
      |                                  |  Vérification d'autorisation  |
    21 => "month",
      |                                  +------------------------------>|
    22 => "numberMonth",
      |                                  |                              |
    23 => "oneValidityTypeId",
      |                        Données /|Réponse                        |
];
      |<---------------------------------+-------------------------------+
      |                                  |                              |


//Session cookies are used to store information necessary for the authorization code flow
===Demande de renouvellement d'un jeton===
session_start();
Pour obtenir un renouvellement de jeton d'accès, il faut effectuer la requête suivante : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.


/**
Les paramètres suivants sont nécessaires :
* This function is used to make api calls to the RS
{| class="wikitable"
*
!Nom!!Type!!Description
* @param      $url
|-
* @param      $post
|client_id||string||L'identifiant ''client_id'' reçu pendant l'enregistrement du client.
* @param array $headers
|-
*
|client_secret||string||La passphrase ''client_secret'' reçue pendant l'enregistrement du client.
* @return mixed
|-
*/
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, la valeur doit être "refresh_token" (sans guillements).
function apiRequest($url, $post = null, $token = false, $auth = true, $headers = array())
|-
|scope||string||('''OPTIONNEL''') Liste des droits demandés par le client, séparés par des espaces.
|}
 
Si la requête est correcte, un jeton d'accès ''access_token'' est fourni en réponse dans un objet au format JSON.
<syntaxhighlight lang="javascript">
{
{
    $ch = curl_init($url);
  "token_type": "Bearer",
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  "expires_in": 3600,
    curl_setopt($ch, CURLOPT_HEADER, true);
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}
</syntaxhighlight>


    $method = 'get';
==Liste des scopes disponibles==
Un scope sur OAuth2 correspond à un droit d'accès sur une ressource particulière. Chaque scope est unique et indique de manière explicite le privilège qu'il donne. Il n'y a aucune restriction d'utilisation des scopes par rapport aux mécanismes. Chaque scope peut être utilisé avec n'importe quel mécanisme. Il n'y a que des recommandations vis à vis de leur utilisation. OpenFlyers définit une liste de scopes dédiée aux ressources accessibles par les clients. Ces scopes sont les suivants :
{| class="wikitable"
!Nom!!Description
|-
|default.login||Comportement par défaut lorsqu'aucun scope n'est précisé ou qu'un scope est invalide. Ce scope est recommandé pour '''Authorization Code'''.
|-
|genericreports.readonly||Accéder aux rapports génériques autorisés pour le client en lecture seule. Ce scope est recommandé pour '''Client Credentials'''.
|-
|reports.readonly||Accéder aux rapports personnalisés autorisés pour le client en lecture seule. Ce scope est recommandé pour '''Client Credentials'''.
|}
==Prolonger la durée de vie de la session du client de démonstration==
La session du client de démonstration est initiée avec la méthode PHP [https://www.php.net/manual/en/function.session-start.php session_start]. Cette session peut être interrompue soit en cliquant sur le bouton de déconnexion, soit en fermant le navigateur (car les cookies associés à cette session se détruisent par défaut lorsque le navigateur est fermé).


    if ($post) {
Pour permettre à la session de rester active même après la fermeture du navigateur, il faut utiliser la méthode PHP [https://www.php.net/manual/en/function.session-set-cookie-params.php session_set_cookie_params]. Cette méthode donne la possibilité de définir une durée de vie pour les différents cookies associés à la session.
        $method  = 'post';
<syntaxhighlight lang="php">
        $postData = http_build_query($post);
// Set the parameters for the session cookie in a PHP session in order to configure its lifetime in 30 days
        curl_setopt($ch, CURLOPT_POST, true);
$oauth2DemoSessionLifeTime = time() + (86400 * 30);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
session_set_cookie_params($oauth2DemoSessionLifeTime);
</syntaxhighlight>


        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
NB: Cette approche n'est pas particulièrement sécurisée et peut présenter des risques de sécurité pour OpenFlyers. Par conséquent, elle doit être utilisée avec précaution.
        $headersToSign['digest']      = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }


    $urlComponents = parse_url($url);
==Révocation de token==
Pour initier la demande de révocation, rediriger le navigateur de l'utilisateur vers l'URL : <code>POST https://openflyers.com/nom-de-plateforme/oauth/revoke.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.


    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
Envoyer également le paramètre suivant:
    $headersToSign['Host']            = $urlComponents['host'];
{| class="wikitable"
    $headersToSign['Date']            = gmdate('D, j M Y H:i:s T');
!Nom!!Type!!Description
|-
|access_token||string||Le jeton d'accès (Chaîne de caractère unique permettant de prouver qu'un client OAuth a le droit d'accéder à une ressource.)
|}
 
<syntaxhighlight lang="php">
apiRequest(
                $config['revoke_uri'],
                ['access_token' => $_SESSION['auth_token']['access_token']],
                null,
                false
             );
</syntaxhighlight>


    // generating the signature header
La demande de révocation se fait quand l'utilisateur clique sur le bouton '''Se déconnecter''' pour l'un des deux Clients '''Authorization Code''' et '''Client Credentials'''.
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
    $privateKey          = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);


    $headers['Accept']    = 'application/json';
Si les jetons sont révoqués, l'utilisateur ne peut pas accéder à la plateforme OpenFlyers, il doit se reconnecter.
    $headers['User-Agent'] = $GLOBALS['baseURL'];
    unset($headersToSign['(request-target)']);
    $headers              += $headersToSign;


    if ($token) {
==Utiliser l'API==
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
Une fois un jeton d'accès obtenu, les données sont accessibles par une requête POST exécutée vers l'URL : <code>https://openflyers.com/nom-de-plateforme/oauth/resources.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée. La requête POST doit respecter une structure particulière. Cette structure diffère en fonction du type de ressource souhaité et chaque ressource ne peut être accédée qu'[[#Liste-des-scopes-disponibles|avec le scope qui lui est associé]]. Le jeton d'accès doit être renseigné dans l'en-tête HTTP ''Authorization'' en y indiquant la valeur complète du jeton d'accès reçu précédé du type de jeton reçu  : <code>Authorization: <token_type> <access_token></code>.
    }


    // formatting the headers
===Récupérer les informations de l'utilisateur connecté===
    $httpFormattedHeaders = [];
Ce type de ressource est nommé '''user_information'''. Cette ressource n'est accessible qu'avec le scope '''default.login'''. Cette ressource permet la récupération des informations de l'utilisateur connecté, en d'autre termes, l'utilisateur connecté lors de l'étape d'autorisation et correspondant à celui ayant émis la demande de jeton d'accès. À l'heure actuelle, seul l'identifiant de l'utilisateur connecté est retourné. La requête POST est construite de la manière suivante :
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }


    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
{| class="wikitable"
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);
!Nom!!Type!!Description
|-
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''user_information'''.
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|}


    // for development environment only
Un exemple de requête complète au format JSON :
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
<syntaxhighlight lang="javascript">
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
    //curl_setopt($ch, CURLOPT_VERBOSE, true);
Content-Type: application/x-www-form-urlencoded
 
Host: openflyers.com
    // defining TLS client certificates for Mutual TLS
Date: Wed, 04 Aug 2021 13:57:51 GMT
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
Accept: application/json
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
User-Agent: curl/7.64.1
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
          algorithm="rsa-sha256",
          headers="host content-type digest",
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=


     $response = curl_exec($ch);
{
     "resource_type":"user_information",
    "client_id":"d2615fe2020ec476"
}
</syntaxhighlight>


    // logging errors and responses
La réponse est retournée dans un conteneur JSON.
    errorLog(true, $response, $ch);


    curl_close($ch);
===Récupérer les rapports génériques===
Ce type de ressource est nommé '''generic_report'''. Cette ressource n'est accessible qu'avec le scope '''genericreports.readonly'''. Cette ressource permet la récupération des rapports génériques au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :


    // in case an authentication request is executed
{| class="wikitable"
    if ($auth) {
!Nom!!Type!!Description
        list($responseHeader, $responseBody) = explode("\r\n\r\n", $response, 2);
|-
 
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''generic_report'''.
        $responseHeadersDigest = '';
|-
        $responseHeadersSignature = '';
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
        // extracting digest and signature from headers
|-
        $responseHeadersArray = explode("\r\n", $responseHeader);
|report_id||int||Identifiant du rapport souhaité.
        $responseProtocol = array_shift($responseHeadersArray);
|-
|replacementList||array||Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.
|}


        foreach ($responseHeadersArray as $value) {
Un exemple de requête complète au format JSON :
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
<syntaxhighlight lang="javascript">
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
        }
Content-Type: application/x-www-form-urlencoded
 
Host: openflyers.com
        $responseDigestAlgo = '';
Date: Wed, 04 Aug 2021 13:57:51 GMT
        $responseDigestKey = '';
Accept: application/json
        foreach ($responseHeaders as $key => $value) {
User-Agent: curl/7.64.1
            if ($key === "digest") {
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
                $responseHeadersDigest = $value;
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
                // stripping SHA algorithm for later comparison
          algorithm="rsa-sha256",
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
          headers="host content-type digest",
            } else if ($key === "signature")
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
                $responseHeadersSignature = $value;
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=
        }
 
{
    "resource_type":"generic_report",
    "client_id":"d2615fe2020ec476",
    "report_id":135,
    "replacementList":{
        "year":2018
    }
}
</syntaxhighlight>
 
La réponse est un fichier CSV retourné dans un conteneur JSON.


        // calculating response digest
Le report_id se trouve dans la [[Bibliothèque-des-rapports#Présentation|bibliothèque des rapports]] dans la 1ère colonne de la ligne du rapport concerné.
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));


        // checking digest validity
Pour identifier les paramètres à transmettre dans "replacementList", il faut retrouver le rapport [[OF-doc-en:Export-generator-4|dans la version anglaise de la documentation]].
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }


        // extracting variables from signature header
Exemple avec le rapport [[OF-doc-en:Accounting-exports#Balances-of-resource-accounts|"Balances of resource accounts"]] : le chapitre indique que les paramètres/variables nécessaires sont ''accountingId'' et ''endDate''.
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }


        // generating siging string
===Récupérer les rapports personnalisés===
        $signingStringArray = explode(" ", $signature['headers']);
Ce type de ressource est nommé '''report'''. Cette ressource n'est accessible qu'avec le scope '''reports.readonly'''. Cette ressource permet la récupération des rapports personnalisés au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }


        // trimming last '\n' character
{| class="wikitable"
        $signingString = trim($signingString);
!Nom!!Type!!Description
 
|-
        // decoding signature
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''report'''.
        $decodedSignature = base64_decode($signature['signature']);
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|report_id||int||Identifiant du rapport souhaité.
|-
|replacementList||array||Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.
|}


        // verifying signature
Un exemple de requête complète au format JSON :
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');
<syntaxhighlight lang="javascript">
POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
          algorithm="rsa-sha256",
          headers="host content-type digest",
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=


        if (!$signatureVerify) {
{
            errorLog(false, false, "Signature is not correct");
    "resource_type":"report",
            while (($err = openssl_error_string()))
    "client_id":"d2615fe2020ec476",
                errorLog(false, false, $err);
    "report_id":1,
            die();
    "replacementList":{
         }
         "year":2020
 
        return json_decode($responseBody, true);
     }
     }
    return $response;
}
}
</syntaxhighlight>


/**
La réponse est un fichier CSV retourné dans un conteneur JSON.
* This function logs curl responses and errors in text files
*
* @param mixed $response the response
* @param mixed $request  the request handle, used to get errors
*/
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;


    if ($response === false) {
=Procédures=
        if ($curl)
==Créer un client à partir du code source==
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
;Prérequis
        else
Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
 
    } else {
;Télécharger Le code source du client de démonstration :
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
 
    }
Le code source est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
}
 
Le code source est structuré de la manière suivante :
 
[[File:Oauth2 src demo folder.png|800px]]


/**
* Le dossier '''css''' contient tout le matériel nécessaire à la stylisation de la page web.
* This function generates the full signature header line from a set of headers, a certificate and the linked private key
* Le dossier '''img''' contient les images affichées sur la page web.
*
* Le dossier '''ssl''' est le dossier contenant tous les certificats et les clé privées associées à chaque client. Il doit respecter une structure particulière décrite ci-dessous.
* @param array  $headersToSign the headers to sign, in the $key => $value format
* Le fichier '''ClientDemo.php''' contient la classe '''ClientDemo'''. Cette classe contient toutes les méthodes nécessaires au fonctionnement du client de démonstration OAuth2.
* @param string $certificate  the certificate linked to the used private key
* Le fichier '''index.php''' est le fichier à appeler depuis le navigateur. Ce fichier correspond au fichier qui gère les appels à la classe '''ClientDemo''' et exécute les méthodes dans l'ordre.
* @param string $privateKey    the private key used to sign the headers
 
*
Le dossier '''ssl''' doit respecter la structure suivante :
* @return string the full signature header line
*/
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers        = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers            .= $normalizedHeaderKey . ' ';
        $signatureString    .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers        = trim($headers);
    $signatureString = trim($signatureString);


    // signing the signing string
[[File:Oauth2 demo tree.png]]
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);


    // compiling the header line
;[[#Générer des certificats|Générer les certificats]] :
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
Après la génération des certificats, les clés privées 'auth.key' et 'sign.key' remplacent celles présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo'.
}


/**
;[[#Enregistrer un client|Enregistrer les clients]] :
* This function deletes the access token from the session
*
* @param string $baseURL
*/
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');


    header('Location: ' . $baseURL);
*Deux clients doivent être créés :
}
**Le premier pour le mécanisme d'autorisation '''Authorization Code''',
**Le second pour le mécanisme d'autorisation '''Client Credentials'''.


function displayMenuLoggedIn(): void
[[File:Oauth2 demo manage.png|800px]]
{
    global $replacementListItems;
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';


    echo '<form id="view_repos" method="post">';
*Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton '''Télécharger le certificat CA''' de la page de gestion. Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton '''Télécharger le certificat de signature du serveur''' de la page de gestion. Placer les deux certificats téléchargés à la racine du dossier '''ssl'''.
    echo '<label for="report_id">Report ID: </label>';
    echo '<input type="number" id="report_id" name="report_id"><br><br>';
    foreach ($replacementListItems as $value) {
        echo '<label for="' . $value . '">' . $value . ': </label>';
        echo '<input type="text" id="' . $value . '" name="' . $value . '"><br><br>';
    }
    echo '<input type="hidden" id="action" name="action" value="view">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';


    echo '<form id="logout" method="post">';
*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Authorization Code''' et les placer dans le répertoire '''ssl/AuthCodeDemo'''.
    echo '<input type="hidden" id="action" name="action" value="logout">';
*Modifier le fichier '''ssl/AuthCodeDemo/config.authcode.json''' en le remplissant de la manière suivante :
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
<syntaxhighlight lang="php">
    echo '</form>';
}
 
function displayMenuLoggedOut(): void
{
{
    echo '<h2><a href="/">Home</a></h2>';
  "client_id": "XXXXXXXXXXXXXXXX",
    echo '<h3>Not logged in</h3>';
  "client_secret": "XXXXXXXXXXXXXXXX",
    echo '<form id="login" method="post">';
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
    echo '<input type="hidden" id="action" name="action" value="login">';
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
    echo '</form>';
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/AuthCodeDemo/auth.key",
  "sign_cert": "path_to_client/ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
}
</syntaxhighlight>


// display client "home" page
*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
if (!isset($_POST['action'])) {
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.
    if (!empty($localToken['access_token'])) {
*Remplacer <code>path_to_client/</code> par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
        displayMenuLoggedIn();
**Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/</code>.
    } else {
**Exemple sur un serveur Linux '''debian''': <code>./</code>.
        displayMenuLoggedOut();
 
    }
 
*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Client Credentials''' et les placer dans le répertoire '''ssl/ClientCredDemo'''.
*Modifier le fichier '''ssl/ClientCredDemo/config.clientcred.json''' en le remplissant de la manière suivante :
<syntaxhighlight lang="php">
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/ClientCredDemo/auth.key",
  "sign_cert": "path_to_client/ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/ClientCredDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
}
</syntaxhighlight>


if (isset($_POST['action'])) {
*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
    $replacementList = [];
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.
    // Building replacement list
*Remplacer <code>path_to_client/</code> par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
    foreach ($replacementListItems as $key => $value) {
**Exemple sur '''windows''': <code>C:/wamp64/www/4.0/oauth-demo/</code>.
        $replacementList[$value] = (isset($_POST[$value]) && strlen($_POST[$value]) > 0) ? $_POST[$value] : null;
**Exemple sur un serveur Linux '''debian''': <code>./</code>.
    }
 
    // Building query array
 
    $repoQueryParameters = [
*Suivre la [[#Utiliser le client|procédure d'utilisation du client de démonstration]] pour faire fonctionner le client.
        'resource_type' => 'generic_report',
 
        'client_id' => $config['client_id'],
 
        'report_id' => (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0) ? $_POST['report_id'] : null,
'''NB''': Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.
        'replacementList' => $replacementList
 
    ];
==Enregistrer un client==
    switch ($_POST['action']) {
Pour utiliser l'API OAuth2, il faut enregistrer un client OAuth2 auprès d'OpenFlyers. Pour ceci, suivre les étapes suivantes :
        case 'login':
 
            $token = apiRequest(
 
                $config['token_uri'],
;Pour le mécanisme d'authentification Client Credentials
                [
*Créer un [[Gestion-des-profils#Ajouter-un-profil|nouveau profil]]. Ce profil doit permettre de gérer les droits du client OAuth2. Choisir un nom explicite, par exemple "Client OAuth rapports".
                    'grant_type'   => 'client_credentials',
 
                    'client_id'    => $config['client_id'],
*Sélectionner les droits à assigner à ce profil. Ces droits limitent les données auxquelles le client OAuth2 a accès.
                    'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
**Sélectionner les droits relatifs à l'enregistrement de clients OAuth2 dans l'onglet '''Admin''' (colonne '''Associé aux clients OAuth2''').
                    'scope'         => 'genericreports.readonly reports.readonly'
**Sélectionner les rapports accessibles par le profil précédemment créé.
                ]
 
            );
*Créer un nouvel utilisateur à partir du panneau de gestion. Cet utilisateur est virtuel et représente le serveur sur lequel fonctionne le client OAuth2.
            // We store the access token in the session so the user is "connected"
**''Des identifiant et nom explicites sont recommandés (exemple : "serv1_oauth_client")''
            // Only if the request has been successfully executed
**'''Attention''' : tout utilisateur désactivé et associé à un client OAuth2 rend le client inactif, il faut donc changer l'utilisateur associé au client pour réactiver le client.
            if (isset($token['access_token'])) {
                file_put_contents($localTokenFile, json_encode($token, true));


                // Redirecting the user's browser to the "home" page
                header('Location: ' . $baseURL);
            } else {
                echo $token;
            }
            break;
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );


            // Separating headers from body
;Pour le mécanisme d'authentification Authorization Code
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
*Aller dans '''Admin > Utilisateurs > Profils'''
*Dans l'onglet '''Généralités''', cocher la case relative à la colonne '''Connexion depuis l'extérieur (OAuth2)''' pour le profil souhaité.


            $resourceHeaders = explode("\r\n", $resourceHeader);
            $firstHeaderLine = array_shift($resourceHeaders);


            // Building form for download CSV button
;Créer un nouveau client OAuth2
            echo '<form method="post">';
*Aller dans '''Admin > Transferts > Exports > API OAuth2'''
            foreach ($_POST as $key => $value) {
[[File:Oauth2 manage.png|800px]]
                if ($key == "action") {
*Cliquer sur le bouton Ajouter '''+''' ou '''Ajouter un client'''
                    $value = "download";
[[File:Oauth2 client creation.png|800px]]
                }
*Choisir un nom pour le client.
                echo '<input type="hidden" id="' . $key . '" name="' . $key . '" value="' . $value . '">';
*Sélectionner le mécanisme d'autorisation utilisé par le client :
            }
**'''Authorization Code''': permet d'utiliser OAuth2 comme solution SSO ou accéder à des données utilisateurs. Cette méthode peut être couplée avec le mécanisme de mémorisation de connexion (''Refresh Token'').
            if (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0 && !isset(json_decode($resourceBody, true)['error'])) {
**'''Client Credentials''': permet d'utiliser OAuth2 dans un contexte d'automatisme.
                echo '<button>Download as CSV</button>';
*Saisir l'URI de redirection vers le client pour le mécanisme ''Authorization Code''.
            }
*Sélectionner l'utilisateur virtuel créé précédemment pour le mécanisme ''Client Credentials''.
            echo '</form>';
*[[#Générer-des-certificats|Générer deux CSR]] afin d'obtenir deux certificats signés et les saisir :
**'''Certificate Signing Request pour le certificat d'authentification''' est utilisé pour l'authentification mutuelle avec mTLS (auth_cert.csr.pem).
**'''Certificate Signing Request pour le certificat de signature''' est utilisé pour la signature des en-têtes HTTP (sign_cert.csr.pem).
 
[[File:PublicKeysCopyScreen.png|900px]]
 
 
*Cliquer sur '''Enregistrer'''.
 
NB: La validité des certificats générés s'étend sur une période de '''3 années'''.
 


            // Displaying requested content
;Sauvegarder le couple ID/passphrase
            echo '<pre>';
Un couple ID/passphrase (client_id/client_secret) est généré. Ces deux clées ne sont communiquées qu'une seule fois. Elle doivent être stockées en toute sécurité et gardées confidentielles,
            echo (strpos($firstHeaderLine, "200")) ? json_decode($resourceBody) : $resourceBody;
et Mettre ces identifiants dans le fichier '''config.clientcred.json'''.
            echo '</pre>';
[[File:Oauth2 client created.png]]
            break;
        case 'download':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );


            // Separating headers from body
[[File:ConfigCredJsonScreen.png|600px]]
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
            $resourceHeaders = explode("\r\n", $resourceHeader);
            array_shift($resourceHeaders);


            // Dumping headers to insert them in current page
            foreach ($resourceHeaders as $key => $value) {
                header($value);
            }
            echo json_decode($resourceBody);
            break;
    }
}</php>


;Ce script a été conçu pour montrer le fonctionnement du mécanisme et tester l'implémentation d'un serveur, il n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.
;Télécharger les certificats crt
Les certificats signés sont téléchargeables depuis l'interface de gestion des clients OAuth2, les certificats sont disponibles dans les onglets '''Certificat d'authentification''' et '''Certificat de signature''' et les mettre dans le dossier '''/ssl''' du client OAuth2.
[[File:Oauth2 client certificates.png]]


==Refresh Token==
;Téléchargez les certificats du serveur.
Refresh Token (ou jeton de rafraîchissement en français) est un mécanisme d'autorisation particulier. Il ne peut fonctionner en tant que tel, il fonctionne de pair avec [[#Authorization-Code|Authorization Code]]. Lorsqu'un jeton d'accès arrive est arrivé en fin de vie, si le client y est autorisé, il peut faire une demande de renouvellement de son jeton d'accès auprès du serveur d'autorisation en présentant son jeton de rafraîchissement. Le serveur d'autorisation vérifie la validité et génère un autre jeton d'accès qu'il transmet au client, sans que l'utilisateur final n'aît à se connecter de nouveau.
*Les certificats du CA d'OpenFlyers et de signature HTTP du serveur sont nécessaires. Ils sont téléchargeables depuis l'interface de configuration des clients OAuth2.
 
[[File:DownloadServerCertifScreen.png|900px]]
Le principe de fonctionnement du rafraîchissement d'un jeton est le suivant :
*Dans certains cas d'utilisation, il peut être nécessaire d'ajouter le certificat du CA d'OpenFlyers au Trust Store du système. Si cette étape n'est pas réalisée, les certificats peuvent être considérés comme invalides et peuvent ne pas être utilisables.
* Le jeton d'accès du client est arrivé à péremption. Le client effectue une demande de renouvellement en transmettant son Refresh Token au serveur d'autorisation.
*Pour ajouter le certificat CA au Trust Store du système, suivre les étapes suivantes:
* Le serveur d'autorisation vérifie la validité des informations, "consomme" le jeton de rafraîchissement et génère une nouvelle paire de jetons
**Sous Linux, copier le certificat CA d'OpenFlyers dans le dossier <code>/usr/local/share/ca-certificates</code> et exécuter la commande <code>sudo update-ca-certificates</code>
* Le client reçoit sa nouvelle paire et utilise le nouveau jeton d'accès pour accéder aux ressources
**Sous Windows,
***Double-cliquer sur le certificat CA d'OpenFlyers téléchargé depuis l'interface d'enregistrement des clients OAuth2
***Cliquer sur '''Installer un certificat...'''
***Choisir l'emplacement de stockage (utilisateur ou ordinateur) et cliquer sur '''Suivant''' puis '''Suivant''' et enfin '''Terminer'''
 
==Générer des certificats==
L'API OAuth2 implémente [https://tools.ietf.org/html/draft-cavage-http-signatures-10 HTTP Signature] et l'[[Wikipedia-en:Mutual_authentication#mTLS|authentification TLS mutuelle]]. Ces mécanismes utilisent chacun une paire certificat/clé privée différente.
 
Pour obtenir ces certificats, il faut d'abord générer des Certificate Signing Request (CSR).
 
La procédure est la suivante :
*[[OpenSSL#Installer-OpenSSL-dans-un-environnement-Windows|Télécharger OpenSSL pour Windows]] ou [[OpenSSL#Utiliser-Openssl-d'Apache-sous-WAMP|utiliser Openssl d'Apache sous WAMP]].
*Utiliser les deux fichiers de configuration ''sign_cert.conf'' et ''auth_cert.conf'' ci-dessous et les remplir.


    +------+                      +-------------+                  +-----------+
;Fichier de configuration OpenSSL - sign_cert.conf
    |Client|                      |  Serveur  |                  |  Serveur  |
<pre>
    +--+---+                      |Autorisation |                  | Ressources|
[req]
      |                          +------+------+                  +-----+-----+
default_bits      = 4096                    # taille par défaut des nouvelles clés, peut être surchargé dans la commande
      |        Authentification          |                              |
encrypt_key       = no                      # chiffrer la clé générée
       |        + Refresh Token          |                              |
distinguished_name = req_distinguished_name  # pointe vers la catégorie spécifiée pour le Distinguished Name
      +--------------------------------->|                              |
x509_extensions   = v3_req                  # pointe vers la catégorie spécifiée pour les extensions x509
      |                                  |                              |
prompt            = no
      |   Access (+ Refresh) token      |                              |
      |<---------------------------------+                              |
      |                                  |                              |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                              |
      |                                  |                              |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                              |
      |                        Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                              |


===Demande de renouvellement d'un jeton===
[req_distinguished_name]
Pour obtenir un renouvellement de jeton d'accès, il faut effectuer la requête suivante : <code>POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.
C                  =                         # code à deux chiffres du pays (ex: FR)
ST                =                         # région/état (ex: Gironde)
L                  =                         # ville (ex: Bordeaux)
O                  =                         # organisation (ex: OpenFlyers)
OU                =                         # unité organisationelle (ex: IT)
CN                =                         # nom de domaine (ex: openflyers.com)


Les paramètres suivants sont nécessaires :
[v3_req]
{| class="wikitable"
keyUsage          = digitalSignature        # pour quelles opérations la clé peut-elle être utilisée</pre>
!Nom!!Type!!Description
|-
|client_id||string||L'identifiant ''client_id'' reçu pendant l'enregistrement du client.
|-
|client_secret||string||La passphrase ''client_secret'' reçue pendant l'enregistrement du client.
|-
|grant_type||string||Le mécanisme d'autorisation utilisé. Ici, la valeur doit être "refresh_token" (sans guillements).
|-
|scope||string||('''OPTIONNEL''') Liste des droits demandés par le client, séparés par des espaces.
|}


Si la requête est correcte, un jeton d'accès ''access_token'' est fourni en réponse dans un objet au format JSON.
;Fichier de configuration OpenSSL - auth_cert.conf
<javascript>{
<pre>[req]
   "token_type": "Bearer",
default_bits      = 4096                    # taille par défaut des nouvelles clés, peut être surchargé dans la commande
  "expires_in": 3600,
encrypt_key        = no                      # chiffrer la clé générée
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
distinguished_name = req_distinguished_name   # pointe vers la catégorie spécifiée pour le Distinguished Name
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
x509_extensions    = v3_req                  # pointe vers la catégorie spécifiée pour les extensions x509
}</javascript>
prompt            = no
 
[req_distinguished_name]
C                  =                          # code à deux chiffres du pays (ex: FR)
ST                =                          # région/état (ex: Gironde)
L                  =                          # ville (ex: Bordeaux)
O                  =                          # organisation (ex: OpenFlyers)
OU                =                          # unité organisationelle (ex: IT)
CN                =                          # nom de domaine (ex: openflyers.com)
 
[v3_req]
extendedKeyUsage  = clientAuth              # pour quelles opérations la clé peut-elle être utilisée</pre>


==Liste des scopes disponibles==
Un scope sur OAuth2 correspond à un droit d'accès sur une ressource particulière. Chaque scope est unique et indique de manière explicite le privilège qu'il donne. Il n'y a aucune restriction d'utilisation des scopes par rapport aux mécanismes. Chaque scope peut être utilisé avec n'importe quel mécanisme. Il n'y a que des recommandations vis à vis de leur utilisation. OpenFlyers définit une liste de scopes dédiée aux ressources accessibles par les clients. Ces scopes sont les suivants :
{| class="wikitable"
!Nom!!Description
|-
|default.login||Comportement par défaut lorsqu'aucun scope n'est précisé ou qu'un scope est invalide. Ce scope est recommandé pour '''Authorization Code'''.
|-
|genericreports.readonly||Accéder aux rapports génériques autorisés pour le client en lecture seule. Ce scope est recommandé pour '''Client Credentials'''.
|-
|reports.readonly||Accéder aux rapports personnalisés autorisés pour le client en lecture seule. Ce scope est recommandé pour '''Client Credentials'''.
|}
==Révocation de token==
Pour initier la demande de révocation, rediriger le navigateur de l'utilisateur vers l'URL : <code>POST https://openflyers.com/nom-de-plateforme/oauth/revoke.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée.


Envoyer également le paramètre suivant:
Exécuter les commandes suivantes :
{| class="wikitable"
*<bash>openssl req -sha256 -newkey rsa -keyout sign.key -out sign_cert.csr.pem -outform PEM -config sign_cert.conf</bash>
!Nom!!Type!!Description
*<bash>openssl req -sha256 -newkey rsa -keyout auth.key -out auth_cert.csr.pem -outform PEM -config auth_cert.conf</bash>
|-
|access_token||string||Le jeton d'accès (Chaîne de caractère unique permettant de prouver qu'un client OAuth a le droit d'accéder à une ressource.)
|}
<php> apiRequest(
                $config['revoke_uri'],
                ['access_token' => $_SESSION['auth_token']['access_token']],
                null,
                false
            );</php>


La demande de révocation se fait quand l'utilisateur clique sur le bouton '''Se déconnecter''' pour l'un des deux Clients '''Authorization Code''' et '''Client Credentials'''.
Ces commandes prennent chacune en entrée le fichier de configuration et génèrent une clé privée et un Certificate Signing Request.  


Si les jetons sont révoqués, l'utilisateur ne peut pas accéder à la plateforme OpenFlyers, il doit se reconnecter.
Une fois ces CSR obtenus :
*Les renseigner dans les champs prévus à cet effet lors de la [[#Enregistrer-un-client|création d'un client]] et télécharger les certificats signés depuis l'interface une fois le client créé.
*Garder la clé privée confidentielle. Une fuite poserait un risque de sécurité. Elle va de paire avec le certificat distribué par l'autorité de certification OpenFlyers.


==Utiliser l'API==
==Mettre en place une connexion à l'API OpenFlyers sur un serveur mutualisé==
Une fois un jeton d'accès obtenu, les données sont accessibles par une requête POST exécutée vers l'URL : <code>https://openflyers.com/nom-de-plateforme/oauth/resources.php</code>. Remplacer <code>nom-de-plateforme</code> par le nom de la plateforme utilisée. La requête POST doit respecter une structure particulière. Cette structure diffère en fonction du type de ressource souhaité et chaque ressource ne peut être accédée qu'[[#Liste-des-scopes-disponibles|avec le scope qui lui est associé]]. Le jeton d'accès doit être renseigné dans l'en-tête HTTP ''Authorization'' en y indiquant la valeur complète du jeton d'accès reçu précédé du type de jeton reçu  : <code>Authorization: <token_type> <access_token></code>.
;Note
La procédure ci-après est destinée à une mise en place lorsqu'il n'y a pas d'accès SSH en ligne de commande mais uniquement un accès FTP. Dans ce cas, la création des clés privées et publics est effectuée "en local". Dans la procédure suivante elle est effectuée depuis un PC sous '''Windows'''.


===Récupérer les informations de l'utilisateur connecté===
;Prérequis
Ce type de ressource est nommé '''user_information'''. Cette ressource n'est accessible qu'avec le scope '''default.login'''. Cette ressource permet la récupération des informations de l'utilisateur connecté, en d'autre termes, l'utilisateur connecté lors de l'étape d'autorisation et correspondant à celui ayant émis la demande de jeton d'accès. À l'heure actuelle, seul l'identifiant de l'utilisateur connecté est retourné. La requête POST est construite de la manière suivante :
*Posséder les accès FTP :
**Hôte : XXXXXXXXXXXXXXXXXX
**Login : XXXXXXXX
**Mot de passe :  XXXXXXXX
**Port : XX (par exemple 21)
*Télécharger Le code source du client de démonstration à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip


{| class="wikitable"
;Procédure
!Nom!!Type!!Description
*[[#Générer des certificats|Générer les certificats]] en local.
|-
*Remplacer les clés privées 'auth.key' et 'sign.key' présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo' par les clés générées.
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''user_information'''.
*[[#Enregistrer un client|Enregistrer les deux clients]] :
|-
**Le premier pour le mécanisme d'autorisation '''Authorization Code'''.
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
**Le second pour le mécanisme d'autorisation '''Client Credentials'''.
|}


Un exemple de requête complète au format JSON :
[[File:Oauth2 demo manage.png|800px]]
<javascript>POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
 
Content-Type: application/x-www-form-urlencoded
*Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton '''Télécharger le certificat CA''' de la page de gestion.  
Host: openflyers.com
*Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton '''Télécharger le certificat de signature du serveur''' de la page de gestion.
Date: Wed, 04 Aug 2021 13:57:51 GMT
*Placer les deux certificats téléchargés à la racine du dossier '''ssl'''.
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
          algorithm="rsa-sha256",
          headers="host content-type digest",
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=


*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Authorization Code''' et les placer dans le répertoire '''ssl/AuthCodeDemo'''.
*Modifier le fichier '''ssl/AuthCodeDemo/config.authcode.json''' en le remplissant de la manière suivante.
<syntaxhighlight lang="php">
{
{
    "resource_type":"user_information",
  "client_id": "XXXXXXXXXXXXXXXX",
    "client_id":"d2615fe2020ec476"
  "client_secret": "XXXXXXXXXXXXXXXX",
}</javascript>
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "./ssl/AuthCodeDemo/auth.key",
  "sign_cert": "./ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "./ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}
</syntaxhighlight>


La réponse est retournée dans un conteneur JSON.


===Récupérer les rapports génériques===
*Télécharger les deux certificats ''Certificat d'authentification'' et ''Certificat de signature'' du client '''Client Credentials''' et les placer dans le répertoire '''ssl/ClientCredDemo'''.
Ce type de ressource est nommé '''generic_report'''. Cette ressource n'est accessible qu'avec le scope '''genericreports.readonly'''. Cette ressource permet la récupération des rapports génériques au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :
*Modifier le fichier '''ssl/ClientCredDemo/config.clientcred.json''' en le remplissant de la manière suivante.
<syntaxhighlight lang="php">
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "./ssl/ClientCredDemo/auth.key",
  "sign_cert": "./ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "./ssl/ClientCredDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}
</syntaxhighlight>
 
*Remplacer les <code>XXXXXXXXXXXXXXXX</code> des champs <code>client_id</code> et <code>client_secret</code> par les valeurs obtenues lors de [[#Enregistrer-un-client|l'enregistrement du client]].
*Remplacer <code>mastructure</code> par le nom de la structure sur laquelle la démo est testée.
 
*Transférer le fichier "oauth-demo" vers le serveur mutualisé :
**Télécharger [http://filezilla-project.org/download.php?type=client FileZilla].
**Lancer FileZilla.
**Entrer l'URl du serveur mutualisé dans le champ '''Hôte'''.
**Entrer le login dans le champ '''Nom d'utilisateur'''.
**Entrer le mot de passe dans le champ '''Mot de passe'''.
**Entrer le port dans le champ '''Port'''.
**Cliquer sur le bouton Connexion.
**Accéder à l'emplacement du répertoire "oauth-demo" en local à gauche dans l'onglet "Site local".
**Choisir l'emplacement où placer le répértoire oauth-demo dans l'anglet '''Site distant''' à droite.
**Glisser et déposer le oauth-demo à l'emplacement choisi.
[[File:Transfer OauthDemo To Shared Server.png|800px]]
*Accéder au client OAuth-demo depuis le serveur mutualisé en utilisant l'URL du domaine du serveur : url_de_domaine_de_serveur/oauth-demo/index.php
*Modifier la valeur '''URI de redirection vers le client''' du client '''AuthCodeDemo''' précédemment créé en remplaçant l'ancienne URL par la nouvelle.


{| class="wikitable"
*Suivre la [[#Utiliser le client|procédure d'utilisation du client de démonstration]] pour faire fonctionner le client.
!Nom!!Type!!Description
 
|-
 
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''generic_report'''.
'''NB''': Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.
|-
 
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
==Récupérer les données d'un utilisateur==
|-
*Cliquer sur le bouton '''Récupérer les informations utilisateur''', l'identifiant de l'utilisateur s'affiche.
|report_id||int||Identifiant du rapport souhaité.
*Utiliser l'identifiant récupérer afin de récupérer toute information associée à cet utilisateur en [[Gestion-des-rapports#Ajouter-un-rapport|créant de nouveaux rapports personnalisés]]
|-
 
|replacementList||array||Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.
==Utiliser le client==
|}
;Prérequis
Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php
 
Le client de démonstration se présente de la manière suivante.
 
[[File:Oauth2-client-demo.png]]
 
La démonstration est composée de deux colonnes. La première, nommée '''Authorization Code''' correspond [[#Authorization Code|au mécanisme d'autorisation du même nom]]. Elle dispose d'un bouton permettant de se connecter ainsi que d'une section indiquant les informations relatives à l'état de la connexion. La seconde colonne, nommée '''Client Credentials''' correspond elle aussi [[#Client Credentials|au mécanisme d'autorisation du même nom]]. Comme pour la première colonne, les éléments qui y sont présentés sont identiques. La différence étant que le bouton de connexion n'a pas le même effet étant donné que ces deux mécanismes sont différents. Chaque mécanisme est indépendant et il est possible de se connecter à un des deux mécanismes sans se connecter à l'autre ou se connecter aux deux en même temps.
 
;Le mécanisme '''Authorization Code'''
*Cliquer sur le bouton '''Se connecter''' (ce qui redirige le navigateur vers la page de connexion du logiciel OpenFlyers).
*Renseigner les identifiants de l'administrateur pour s'y connecter.
*Nom d'utilisateur : '''admini'''.
*Mot de passe : '''azerty'''.
 
Une fois les informations saisies, la page suivante est affichée.
 
[[File:Oauth authorize demo.png]]
 
*Cliquer sur le bouton '''Autoriser l'application''' pour autoriser la connexion (ce qui se redirige le navigateur vers la page du client de démonstration OAuth2).
 
La première colonne doit afficher l'état de connexion '''Connecté''' ainsi qu'un nouveau bouton '''Récupèrer les informations utilisateurs''' qui permet de récupérer les informations de l'utilisateur connecté.
 
;Le mécanisme '''Client Credentials'''
*Cliquer sur le bouton de connexion '''Se connecter''': contrairement à celui du mécanisme '''Authorization Code''', ne redirige pas le navigateur vers la page de connexion du logiciel OpenFlyers. Le bouton de connexion utilise les identifiants du client, ici le couple clé privée/clé publique, pour initier la connexion avec le serveur d'autorisation et obtenir un jeton d'accès.
 
Une fois la connexion établie, la seconde colonne doit afficher l'état de connexion '''Connecté''' ainsi qu'un menu déroulant '''Rapport à récupèrer''' et un nouveau bouton '''Récupèrer le rapport''' qui permet de récupérer les rapports génériques et personnalisés.


Un exemple de requête complète au format JSON :
<javascript>POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
          algorithm="rsa-sha256",
          headers="host content-type digest",
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=


{
Le client, une fois connecté sur les deux mécanismes, se présente de la manière suivante.
    "resource_type":"generic_report",
    "client_id":"d2615fe2020ec476",
    "report_id":135,
    "replacementList":{
        "year":2018
    }
}</javascript>


La réponse est un fichier CSV retourné dans un conteneur JSON.
[[File:Oauth2 connected demo.png]]


===Récupérer les rapports personnalisés===
=Troubleshooting=
Ce type de ressource est nommé '''report'''. Cette ressource n'est accessible qu'avec le scope '''reports.readonly'''. Cette ressource permet la récupération des rapports personnalisés au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :
==500 Internal Server Error en récupérant le rapport==
La démo utilise les valeurs par défaut pour extraire les rapports. Une erreur 500 indique une "Erreur de syntaxe ou violation d'accès" lors de l'exécution de la requête du rapport. Cela se produit parce que le rapport n'a pas de valeurs par défaut associées, étant donné qu'il n'a jamais été visualisé dans l'interface web. Pour résoudre ce problème, il vous suffit de visualiser le rapport et de cocher la case "Mémoriser ce choix".


{| class="wikitable"
==Erreur "File not found."==
!Nom!!Type!!Description
Cette erreur se produit lorsque l'URI utilisé n'existe pas sur le serveur OpenFlyers. Vérifier les URIs mis en place dans les fichiers de configuration et essayer de nouveau.
|-
|resource_type||string||Le type de ressource demandé, ici, ce champ correspond à '''report'''.
|-
|client_id||string||L'identifiant unique reçu pendant l'enregistrement du client.
|-
|report_id||int||Identifiant du rapport souhaité.
|-
|replacementList||array||Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.
|}


Un exemple de requête complète au format JSON :
==int_rsa_verify : longueur de signature incorrecte==
<javascript>POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Ce problème pourrait survenir si les fichiers ca.cert et sign_cert_server.cert ne proviennent pas du même serveur que celui du client oauth2.
Content-Type: application/x-www-form-urlencoded
La solution est :
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
          algorithm="rsa-sha256",
          headers="host content-type digest",
          signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=


{
*D'essayer depuis le début l'étape de [[#Générer-des-certificats|génération]] et de [[#Enregistrer-un-client|configuration des certificats]] avec jsut la modification du client existant.
    "resource_type":"report",
    "client_id":"d2615fe2020ec476",
    "report_id":1,
    "replacementList":{
        "year":2020
    }
}</javascript>
 
La réponse est un fichier CSV retourné dans un conteneur JSON.


=Troubleshooting=
;Si cela ne fonctionne pas:


==Erreur "File not found."==
*Essayez de créer [[#Enregistrer-un-client|un nouveau client]] et refaites la configuration des certificats.
Cette erreur se produit lorsque l'URI utilisé n'existe pas sur le serveur OpenFlyers. Vérifier les URIs mis en place dans les fichiers de configuration et essayer de nouveau.

Latest revision as of 13:34, 23 April 2025

Présentation

L'objet de cette page est de décrire l'API OpenFlyers.

Description de l'API

OpenFlyers possède une API basée sur OAuth2 qui permet à des serveurs extérieurs, dûment enregistrés, de mettre en œuvre un processus d'authentification unique (SSO) et/ou de récupération des résultats des requêtes SQL de la bibliothèque des rapports ou des rapports personnalisés sous la forme de fichiers CSV.

OAuth2 propose plusieurs mécanismes pour permettre l'authentification. Un mécanisme d'authentification détermine la séquence exacte des étapes impliquées dans le processus d'authentification d'OAuth2. OpenFlyers met à disposition deux mécanismes d'authentification :

  • Authorization Code basé sur la méthode d'authentification par code d'autorisation et qui correspond au mécanisme associé à l'authentification unique (SSO),
  • Client Credentials basé sur la méthode d'authentification avec les identifiants clients et qui est utilisé dans un contexte d'automatisme sans autorisation de l'utilisateur au préalable.

Dans les chapitres qui suivent, le terme ressource fait référence à la définition OAuth2. Une ressource dans OAuth2 est un élément qui peut être :

  • une ou des données comme des photos, des documents, des contacts ou des informations personnelles,
  • un ou plusieurs services comme des transferts de fonds, la récupération de rapports ou l'ajout d'articles sur un blog,
  • toute ressource nécessitant un accès restreint.

OpenFlyers définit plusieurs types de ressources :

OAuth2 dispose de scopes. Un scope est un privilège définit de manière explicite permettant l'accès à une ressource protégée. OpenFlyers met à disposition une liste de scopes utilisables à travers l'API.

Deux protocoles de sécurité sont présents dans l'API OpenFlyers :

  • mTLS : il permet d'authentifier le client avec un certificat TLS, en plus d'authentifier le serveur avec un certificat. Ce protocole permet d'éviter les usurpations d'identité.
  • HTTP-Signature : il permet de signer les en-têtes et le corps (lorsqu'il y en a un) des messages échangés afin d'en garantir leur intégrité.
Premiers pas - Client de démonstration

Un client de démonstration est disponible pour comprendre les mécanismes décrits ci-dessous.

L'utilisation de ce client de démonstration est décrite dans la procédure Utiliser le client de cette page.

Le client de démonstration est lui-même accessible à cette adresse : https://openflyers.com/oauth2-demo/index.php

Le code source du client de démonstration est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip

L'utilisation du code source est décrite dans la procédure Créer un client à partir du code source.

Définitions

Authentification TLS Mutuelle (mTLS)

En général dans une communication TLS, seul le serveur a l'obligation de fournir un certificat. Il est également possible pour le client de fournir un certificat. Ce principe s'appelle l'authentification mutuelle et est mise en place avec Mutual TLS (ou mTLS).

OpenFlyers associe un certificat pour l'authentification mutuelle unique à chaque client OAuth2.

Envoyer un certificat client

Côté client, le code suivant peut être utilisé pour fournir à cURL le certificat et la clé correspondante ainsi que le certificat du CA d'OpenFlyers à utiliser pour la connexion :

curl_setopt_array($request, [
    CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
    CURLOPT_CAINFO     => $caCertificatePath,
    CURLOPT_SSLCERT    => $certificatePath,
    CURLOPT_SSLKEY     => $keyPath
]);

À noter : le certificat du CA d'OpenFlyers est nécessaire pour assurer la validité des certificats utilisés.

HTTP Signature

HTTP Signature utilise le principe de la signature numérique pour garantir l'authenticité et l'intégrité du message HTTP.

La signature est générée à l'aide d'une clé privée et vérifiée à l'aide de la clé publique correspondante ou d'un certificat contenant cette clé publique.

HTTP Signature utilise deux en-têtes HTTP :

  • Signature : contient la signature et ses métadonnées.
  • Digest : contient le corps du message haché.

Digest

Le digest est calculé comme ceci : digest = base64encode(sha256(corps du message))

Et l'en-tête est structuré de la manière suivante : Digest: SHA-256=<digest>

Un autre algorithme de hachage peut être utilisé, SHA-256 reste cependant le plus répendu.

Exemple en php
$digestHeader = 'Digest: SHA-256=' . base64_encode(hash('sha256', $postData, true));

Signature

L'en-tête HTTP de signature est structuré de la manière suivante : Signature: keyId="<keyId>",algorithm="<algo>",headers="<signed_headers>",signature="<signature>"

Le champ keyId correspond à un identifiant permettant l'identification de la clé utilisée pour vérifier la signature. Pour l'API d'OpenFlyers, sa valeur correspond à l'empreinte SHA-1 du certificat au format PEM à utiliser pour vérifier la signature.

L'algorithme algo correspond à celui utilisé pour générer la signature, exemple : rsa-sha256.

La valeur de signed_headers correspond à la liste des en-têtes inclus dans la signature séparés d'un espace. Exemple : date digest (request-target)

Pour générer la signature, une chaîne de caractères appelée signing string contenant les en-têtes au format lowercase_header_name: value séparés par une nouvelle ligne au format LF (\n) est d'abord générée. Exemple avec les en-têtes "date" et "(request-target)" :

(request-target): post /some/uri\ndate: Tue, 07 Jun 2014 20:51:35 GMT

La signature est ensuite générée comme ceci : base64encode(algo(signing string))

Exemple en php
function generateSignatureHeader(array $headersToSign, string $certificateFingerprint, string $privateKey): string
{
    // generating the signing string and header list
    $headers         = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers             .= $normalizedHeaderKey . ' ';
        $signatureString     .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }

    // trim extra whitespace
    $headers         = trim($headers);
    $signatureString = trim($signatureString);

    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, $privateKey, 'RSA-SHA256');
    $signature = base64_encode($signature);

    // compiling the header line
    return "Signature: keyId=\"$certificateFingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}

Les variables $certificateFingerprint et $privateKey correspondent respectivement à une empreinte SHA-1 de certificat et à une clé privée, tous deux au format PEM.

La variable $headersToSign est un tableau formaté de la manière suivante :

[
    $headerName => $headerValue,
    ...
]

À noter : les en-têtes sont à signer côté client avec le certificat de signature signé par le CA d'OpenFlyers et dédié au client ainsi que la clé privée associée. Le certificat de signature dédié au client est téléchargeable depuis l'interface de gestion des clients OAuth2. Les en-têtes de la réponse du serveur quant à elles doivent être vérifiées avec le certificat de signature HTTP du serveur, téléchargeable aussi depuis l'interface de configuration des clients OAuth2.

Client OAuth2

Une fois le client OAuth2 configuré sur OpenFlyers, il faut l'utiliser avec un client créé au préalable pour communiquer avec le serveur d'autorisation et l'API.

Plusieurs bibliothèques simplifiant la création d'un client sont disponibles.

Des scripts client basiques écrits en php sont aussi fournis pour les mécanismes authorization_code et client_credentials

Authorization Code

Ce flux OAuth2 se déroule en plusieurs étapes :

  • Le client redirige le navigateur de l'utilisateur vers l'URL d'autorisation.
  • Le navigateur de l'utilisateur est redirigé vers l'URL fournie durant la demande ou durant l'enregistrement du client.
  • Le client récupère un code d'autorisation grâce à la redirection précédente, et échange ce code contre un jeton d'accès auprès du serveur d'autorisation.
  • Le client peut utiliser ce code d'accès :
    • Comme preuve d'authentification pour une solution SSO (Single Sign-On).
    • Pour accéder à des données sur le serveur distant en utilisant l'API.


   +-----------+                   +------+                +-----------+                  +-------------+                  +-----------+
   |Utilisateur|                   |Client|                |Navigateur |                  |   Serveur   |                  |  Serveur  |
   +-----+-----+                   +--+---+                +-----+-----+                  |Autorisation |                  | Ressources|
         |                            |                          |                        +------+------+                  +-----+-----+
         |                            |                          |                               |                               |
         |                            |                   Demande|d'autorisation                 |                               |
         |                            +------------------------->+------------------------------>|                               |
         |                            |                          |                               |                               |
         |                            |Authentification + Formulaire d'autorisation              |                               |
         |<---------------------------+--------------------------+<------------------------------+                               |
         |                            |                          |                               |                               |
         |                            |      Autorisation        |                               |                               |
         +----------------------------+------------------------->+------------------------------>|                               |
         |                            |                          |                               |                               |
         |                            |                      Code|d'autorisation                 |                               |
         |                            |<-------------------------+<------------------------------+                               |
         |                            |                          |                               |                               |
         |                            |                Demande de|token                          |                               |
         |                            +--------------------------+------------------------------>|                               |
         |                            |                          |                               |                               |
         |                            |                 Access (+|Refresh) token                 |                               |
         |                            |<-------------------------+-------------------------------+                               |
         |                            |                          |                               |                               |
         |                            |                          |       Requête vers API        |                               |
         |                            +--------------------------+-------------------------------+------------------------------>|
         |                            |                          |                               |                               |
         |                            |                          |                               |                               |
         |                            |                          |                               |<------------------------------+
         |                            |                          |                               |  Vérification d'autorisation  |
         |                            |                          |                               +------------------------------>|
         |                            |                          |                               |                               |
         |                            |                          |      Données/Réponse          |                               |
         |                            |<-------------------------+-------------------------------+-------------------------------+
         |                            |                          |                               |                               |


Générer les codes pour PKCE

Pour faire fonctionner le client OAuth2, il faut générer deux codes pour l'extension PKCE :

  • Un code_verifier échangé pendant la demande de jeton d'accès.
  • Un code_challenge dérivé du code_verifier et échangé pendant la demande d'autorisation.
Générer le code_verifier
function generateCodeVerifier($length = 128): string
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
    $outputCode = '';

    for ($i = 0; $i < $length; $i++) {
        $index      = random_int(0, strlen($characters) - 1);
        $outputCode .= $characters[$index];
    }

    return $outputCode;
}

Cette fonction permet de générer un code_verifier avec une longueur donnée. Le code_verifier doit avoir une longueur entre 43 et 128 caractères.

Générer le code_challenge
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}

Cette fonction génère le code_challenge à partir du code_verifier fourni.

Demande d'autorisation

Pour initier la demande d'autorisation, rediriger le navigateur de l'utilisateur vers l'URL : GET https://openflyers.com/nom-de-plateforme/oauth/authorize.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Envoyer également les paramètres suivants :

Nom Type Description
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
response_type string Le type de réponse envoyée par le serveur. Doit utiliser la valeur "code" (sans guillements) pour ce mécanisme.
redirect_uri string L'URI (ou l'URL) fourni pendant l'enregistrement du client et vers lequel l'utilisateur est redirigé après la demande d'autorisation.
scope string Liste des droits demandés par le client, séparés par des espaces.
state string Chaîne de caractère aléatoire utilisée pour éviter les attaques CSRF. Fortement recommandé.
code_challenge string Code nécessaire pour le fonctionnement de l'extension PKCE.
code_challenge_method string Méthode utilisée pour générer le code_challenge. Ici, la valeur est "S256".

Demande de jeton d'accès

Après avoir répondu à la demande, l'utilisateur est redirigé vers l'URI fourni pendant l'enregistrement du client. Si la demande est acceptée, un code temporaire : code est fourni, ainsi que le paramètre state fourni pendant la demande avec la même valeur. Si la demande est refusée, un code d'erreur est renvoyé.

Si le paramètre state a une valeur différente de celle envoyée avec la demande, c'est peut-être une tentative d'attaque et il faut refuser la réponse.

Echanger ce code contre un jeton d'accès via l'URL : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Les paramètres suivants sont également nécessaires :

Nom Type Description
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
client_secret string La passphrase reçue pendant l'enregistrement du client.
code string Le code temporaire reçu dans la réponse à la demande d'autorisation.
grant_type string Le mécanisme d'autorisation utilisé. Ici, sa valeur doit être authorization_code
redirect_uri string L'URI (ou l'URL) de redirection fourni pendant l'enregistrement du client.
code_verifier string Le code_verifier utilisé pour générer le code_challenge de la demande d'autorisation.

Si la requête est correcte, un jeton d'accès (Access Token) ainsi qu'un jeton de rafraîchissement (Refresh Token) sont fournis en réponse dans un objet au format JSON.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}

Script client : Authorization Code

Voici un exemple simple de client OAuth2 pour le mécanisme d'authentification par code d'autorisation (Authorization Code).

Fichier de configuration config.authcode.json :

{
  "client_id": "",
  "client_secret": "",
  "authorize_uri": "https://openflyers.com/nom-de-plateforme/oauth/authorize.php",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}

/path/to/client/ est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.

  • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/ssl/AuthCodeDemo/.
  • Exemple sur un serveur Linux debian: ./ssl/AuthCodeDemo/.

Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.

Script PHP :

<?php
$localTokenFile    = 'token.json';
// create token file if it does not exist
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');

$config                 = json_decode(file_get_contents('config.authcode.json'), true);
$localToken             = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']      = $config;

// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$port     = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];

$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
$errorLog           = 'error_log.log';
$responseLog        = 'response_log.log';
$GLOBALS['baseURL'] = $baseURL;

//Session cookies are used to store information necessary for the authorization code flow
session_start();

/**
 * This function is used to make api calls to the Authorization Server and the Resource Server
 *
 * @param       $url
 * @param       $post
 * @param array $headers
 *
 * @return mixed
 */
function apiRequest($url, $post = null, $token = false, $auth = true, $refresh = false, $headers = array())
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);

    $method = 'get';

    if ($post) {
        $method   = 'post';
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
        $headersToSign['digest']       = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }

    $urlComponents = parse_url($url);

    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
    $headersToSign['Host']             = $urlComponents['host'];
    $headersToSign['Date']             = gmdate('D, j M Y H:i:s T');

    // generating the signature header
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
    $privateKey           = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);

    $headers['Accept']     = 'application/json';
    $headers['User-Agent'] = $GLOBALS['baseURL'];
    unset($headersToSign['(request-target)']);
    $headers               += $headersToSign;

    if ($token) {
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
    }

    // formatting the headers
    $httpFormattedHeaders = [];
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }

    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);

    // for development environment only
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);

    // defining TLS client certificates for Mutual TLS
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);

    $response = curl_exec($ch);

    // logging errors and responses
    errorLog(true, $response, $ch);

    curl_close($ch);

    // in case an authentication request is executed
    if ($auth) {
        // required when a refresh token request is issued
        // because there is only one header in the response
        // while there are two for regular auth requests
        if (!$refresh) {
            list($firstResponseHeaders, $secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 3);
            if (!$responseBody) {
                list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
            }
        } else {
            list($secondResponseHeaders, $responseBody) = explode("\r\n\r\n", $response, 2);
        }

        $responseHeadersDigest = '';
        $responseHeadersSignature = '';
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $secondResponseHeaders);
        $responseProtocol = array_shift($responseHeadersArray);

        foreach ($responseHeadersArray as $value) {
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }

        $responseDigestAlgo = '';
        $responseDigestKey = '';
        foreach ($responseHeaders as $key => $value) {
            if ($key === "digest") {
                $responseHeadersDigest = $value;
                // stripping SHA algorithm for later comparison
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
            } else if ($key === "signature")
                $responseHeadersSignature = $value;
        }

        // calculating response digest
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));

        // checking digest validity
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }

        // extracting variables from signature header
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }

        // generating siging string
        $signingStringArray = explode(" ", $signature['headers']);
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }

        // trimming last '\n' character
        $signingString = trim($signingString);

        // decoding signature
        $decodedSignature = base64_decode($signature['signature']);

        // verifying signature
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');

        if (!$signatureVerify) {
            errorLog(false, false, "Signature is not correct");
            while (($err = openssl_error_string()))
                errorLog(false, false, $err);
            die();
        }

        return json_decode($responseBody, true);
    }

    return $response;
}

/**
 * This function logs curl responses and errors in text files
 *
 * @param mixed $response the response
 * @param mixed $request  the request handle, used to get errors
 */
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;

    if ($response === false) {
        if ($curl)
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
        else
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}

/**
 * This function generates the full signature header line from a set of headers, a certificate and the linked private key
 *
 * @param array  $headersToSign the headers to sign, in the $key => $value format
 * @param string $certificate   the certificate linked to the used private key
 * @param string $privateKey    the private key used to sign the headers
 *
 * @return string the full signature header line
 */
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers         = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers             .= $normalizedHeaderKey . ' ';
        $signatureString     .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers         = trim($headers);
    $signatureString = trim($signatureString);

    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);

    // compiling the header line
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}

/**
 * This function takes a code_verifier and outputs the corresponding code_challenge
 *
 * @param string $codeVerifier the generated code_verifier
 *
 * @return string the computed code_challenge
 */
function computeCodeChallenge(string $codeVerifier): string
{
    return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}

/**
 * This function takes an optional string length and outputs a random code_verifier string
 *
 * @param int $length the length of the output code_verifier. Default = 128
 *
 * @return string the code_verifier
 * @throws Exception
 */
function generateCodeVerifier($length = 128): string
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~';
    $outputCode = '';

    for ($i = 0; $i < $length; $i++) {
        $index      = random_int(0, strlen($characters) - 1);
        $outputCode .= $characters[$index];
    }

    return $outputCode;
}

/**
 * This function calculates the entropy of a given string
 *
 * @param string $string  the string for which to calculate the entropy
 * @param string $charset a string with all the usable characters. Default = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~
 *
 * @return float|int
 */
function computeEntropy(string $string, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~')
{
    $chars = str_split($charset);
    $probs = [];

    foreach ($chars as $char) {
        $probs[$char] = floatval(substr_count($string, $char)) / strlen($string);
    }

    $sum = 0.0;
    foreach ($probs as $prob) {
        $sum += $prob != 0 ? $prob * log($prob, 2) : 0.0;
    }

    return -$sum;
}

/**
 * This function calculates the ideal (maximum) entropy for a string of a given length
 *
 * @param int $length length of the string. Default = 128
 *
 * @return float|int
 */
function idealEntropy(int $length = 128)
{
    $prob = 1.0 / $length;

    return -1.0 * $length * $prob * log($prob, 2);
}

/**
 * This function is used to initiate the authentication code flow.
 *
 * @param string $clientID     the client's ID
 * @param string $redirectURL  the URL where to redirect after auth
 * @param string $authorizeURL the target URL to request authorization
 *
 * @throws Exception
 */
function login(string $clientID, string $redirectURL, string $authorizeURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');

    // This unguessable string is used to prevent csrf attacks
    $_SESSION['state'] = bin2hex(random_bytes(16));

    // Generate code_verifier and code_challenge for PKCE    
    $_SESSION['code_verifier']  = generateCodeVerifier();
    $_SESSION['code_challenge'] = computeCodeChallenge($_SESSION['code_verifier']);

    // required parameters for the redirection, redirect_uri is where the browser should be redirected
    // when the user grants (or denies) access, scope are the authorizations (rights) requested
    $params = [
        'response_type'         => 'code',
        'client_id'             => $clientID,
        'redirect_uri'          => $redirectURL,
        'scope'                 => 'default.login',
        'state'                 => $_SESSION['state'],
        'code_challenge'        => $_SESSION['code_challenge'],
        'code_challenge_method' => 'S256'
    ];

    // redirecting the browser to the AS authorization endpoint to obtain the authorization code
    header('Location: ' . $authorizeURL . '?' . http_build_query($params));
}

/**
 * This function deletes the access token from the session
 *
 * @param string $baseURL
 */
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');

    header('Location: ' . $baseURL);
}

function displayMenuLoggedIn(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';

    echo '<form id="view_repos" method="post">';
    echo '<input type="hidden" id="action" name="action" value="view">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';

    echo '<form id="logout" method="post">';
    echo '<input type="hidden" id="action" name="action" value="logout">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}

function displayMenuLoggedOut(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}

// display client "home" page
if (!isset($_POST['action'])) {
    if (!empty($localToken['access_token'])) {
        displayMenuLoggedIn();
    } else {
        displayMenuLoggedOut();
    }
}

if (isset($_POST['action'])) {
    // Building query array for resource dumping
    $repoQueryParameters = [
        'resource_type' => 'user_information',
        'client_id' => $config['client_id']
    ];
    switch ($_POST['action']) {
        case 'login':
            login($config['client_id'], $baseURL, $config['authorize_uri']);
            break;
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server to retreive user information
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );

            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);

            $resourceHeaders = explode("\r\n", $resourceHeader);
            $firstHeaderLine = array_shift($resourceHeaders);

            // Displaying user information
            echo '<pre>';
            echo $resourceBody;
            echo '</pre>';

            // Automatic refreshing token once access token is expired
            if (strpos($firstHeaderLine, "401")) {
                $errorBody = json_decode($resourceBody, true);
                if ($errorBody['error'] == 'access_denied' && isset($errorBody['hint'])) {
                    if ($errorBody['hint'] == 'Access token could not be verified') {
                        $token = apiRequest(
                            $config['token_uri'],
                            [
                                'grant_type'    => 'refresh_token',
                                'refresh_token' => $localToken['refresh_token'],
                                'client_id'     => $config['client_id'],
                                'client_secret' => empty($config['client_secret']) ? null : $config['client_secret']
                            ],
                            false,
                            true,
                            true
                        );

                        // We store the access token in the session so the user is "connected"
                        // Only if the request has been successfully executed
                        if (isset($token['access_token'])) {
                            file_put_contents($localTokenFile, json_encode($token, true));

                            // Redirecting the user's browser to the "home" page
                            header('Location: ' . $baseURL);
                        }
                    }
                }
            }
            break;
    }
}

// Once the browser has been redirected to the AS to ask for the user's authorization, assuming it has been granted,
// the browser will get back here with a "code" parameter in the query string
if (isset($_GET['code'])) {
    // The AS MUST redirect the browser here with the exact same state parameter we sent it before, so we check if it is indeed the same
    // to detect if the oauth flow has been tampered with
    if (!isset($_GET['state']) || $_SESSION['state'] != $_GET['state']) {
        header('Location: ' . $baseURL . '?error=invalid_state');
        die();
    }

    // We communicate directly with the AS to exchange the code received against an access token.
    // The id/secret pair is send to authenticate the client, the redirect_uri is sent to verify the code's validity
    $token = apiRequest(
        $config['token_uri'],
        [
            'grant_type'    => 'authorization_code',
            'client_id'     => $config['client_id'],
            'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
            'redirect_uri'  => $baseURL,
            'code'          => $_GET['code'],
            'code_verifier' => $_SESSION['code_verifier']
        ]
    );

    // We store the access token in the session so the user is "connected"
    // Only if the request has been successfully executed
    if (isset($token['access_token'])) {
        file_put_contents($localTokenFile, json_encode($token, true));

        // Redirecting the user's browser to the "home" page
        header('Location: ' . $baseURL);
    } else {
        echo $token;
    }
    die();
}
Ce script a été conçu pour montrer le fonctionnement du mécanisme afin de tester l'implémentation d'un serveur et n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.

Client Credentials

Ce mécanisme d'autorisation est adapté pour l'automatisation. Il fonctionne de la manière suivante :

  • Le client effectue la demande de jeton d'accès au serveur d'autorisation en fournissant ses identifiants.
  • Le serveur authentifie le client avec les identifiants fournis et renvoie un jeton d'accès.
  • Le client utilise ce jeton d'accès pour accéder à des données via l'API.


   +------+                       +-------------+                  +-----------+
   |Client|                       |   Serveur   |                  |  Serveur  |
   +--+---+                       |Autorisation |                  | Ressources|
      |                           +------+------+                  +-----+-----+
      |        Authentification          |                               |
      |         + Demande token          |                               |
      +--------------------------------->|                               |
      |                                  |                               |
      |          Access token            |                               |
      |<---------------------------------+                               |
      |                                  |                               |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                               |
      |                                  |                               |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                               |
      |                         Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                               |


Demande de jeton d'accès

Pour obtenir un jeton d'accès, il faut effectuer la requête suivante : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Les paramètres suivants sont nécessaires :

Nom Type Description
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
client_secret string La passphrase reçue pendant l'enregistrement du client.
grant_type string Le mécanisme d'autorisation utilisé. Ici, la valeur doit être client_credentials.
scope string Liste des droits demandés par le client, séparé par des espaces.
Ce mécanisme a la particularité de ne pas nécessiter d'URI de redirection, il n'est donc pas utile d'en renseigner un lors de l'enregistrement d'un client.

Si la requête est correcte, un jeton d'accès (Access Token) est fourni en réponse dans un objet au format JSON.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw"
}

Script client : Client Credentials

Voici un exemple simple d'un client OAuth2 pour le mécanisme d'authentification par les identifiants client (Client Credentials).

Fichier de configuration config.clientcred.json :

{
  "client_id": "",
  "client_secret": "",
  "token_uri": "https://openflyers.com/nom-de-plateforme/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/nom-de-plateforme/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/nom-de-plateforme/oauth/revoke.php",
  "auth_cert": "/path/to/client/auth_cert.crt",
  "auth_key": "/path/to/client/auth.key",
  "sign_cert": "/path/to/client/sign_cert.crt",
  "sign_key": "/path/to/client/sign.key",
  "auth_cacert": "/path/to/ca.crt",
  "sign_cert_server": "/path/to/server/sign_cert_server.crt"
}

/path/to/client/ est le chemin d'accès vers le dossier qui contient le code source du client de démonstration.

  • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/ssl/ClientCredDemo/.
  • Exemple sur un serveur Linux debian: ./ssl/ClientCredDemo/.

Ce fichier de configuration doit se situer au même niveau que le script PHP dans le système de fichiers.

Script php :

<?php
$localTokenFile    = 'token.json';
// create token file if it does not exist
if (!file_exists($localTokenFile))
    file_put_contents($localTokenFile, '');

$config                 = json_decode(file_get_contents('config.clientcred.json'), true);
$localToken             = json_decode(file_get_contents($localTokenFile), true);
$GLOBALS['config']      = $config;

// Build the baseURL, accounting for protocol, port, name and endpoint. Used for redirection
$protocol = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$port     = ($protocol == 'https://' && $_SERVER['SERVER_PORT'] == 443)
    || ($protocol == 'http://' && $_SERVER['SERVER_PORT'] == 80) ? '' : ':' . $_SERVER['SERVER_PORT'];

$baseURL            = $protocol . $_SERVER['SERVER_NAME'] . $port . $_SERVER['PHP_SELF'];
$errorLog           = 'error_log.log';
$responseLog        = 'response_log.log';
$GLOBALS['baseURL'] = $baseURL;

$replacementListItems = [
    1 => "year",
    2 => "validityTypeId",
    3 => "icao",
    4 => "profileId",
    7 => "accountingId",
    8 => "paymentType",
    9 => "startDate",
    10 => "endDate",
    11 => "occupiedSeat",
    12 => "date",
    13 => "activityTypeId",
    14 => "age",
    15 => "resourceId",
    16 => "personId",
    17 => "accountId",
    20 => "rightPlacePersonId",
    21 => "month",
    22 => "numberMonth",
    23 => "oneValidityTypeId",
];

//Session cookies are used to store information necessary for the authorization code flow
session_start();

/**
 * This function is used to make api calls to the RS
 *
 * @param       $url
 * @param       $post
 * @param array $headers
 *
 * @return mixed
 */
function apiRequest($url, $post = null, $token = false, $auth = true, $headers = array())
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);

    $method = 'get';

    if ($post) {
        $method   = 'post';
        $postData = http_build_query($post);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        $headersToSign['Content-Type'] = 'application/x-www-form-urlencoded';
        $headersToSign['digest']       = 'SHA-256=' . base64_encode(hash('sha256', $postData, true));
    }

    $urlComponents = parse_url($url);

    $headersToSign['(request-target)'] = $method . ' ' . $urlComponents['path'];
    $headersToSign['Host']             = $urlComponents['host'];
    $headersToSign['Date']             = gmdate('D, j M Y H:i:s T');

    // generating the signature header
    $keyId                = openssl_x509_fingerprint(file_get_contents($GLOBALS['config']['sign_cert']));
    $privateKey           = file_get_contents($GLOBALS['config']['sign_key']);
    $headers['Signature'] = generateSignatureHeader($headersToSign, $keyId, $privateKey);

    $headers['Accept']     = 'application/json';
    $headers['User-Agent'] = $GLOBALS['baseURL'];
    unset($headersToSign['(request-target)']);
    $headers               += $headersToSign;

    if ($token) {
        $headers['Authorization'] = $token['token_type'] . ' ' . $token['access_token'];
    }

    // formatting the headers
    $httpFormattedHeaders = [];
    foreach ($headers as $key => $value) {
        $httpFormattedHeaders[] = trim($key) . ': ' . trim($value);
    }

    curl_setopt($ch, CURLOPT_HTTPHEADER, $httpFormattedHeaders);
    curl_setopt($ch, CURLINFO_HEADER_OUT, true);

    // for development environment only
    //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    //curl_setopt($ch, CURLOPT_VERBOSE, true);

    // defining TLS client certificates for Mutual TLS
    curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_setopt($ch, CURLOPT_CAINFO, $GLOBALS['config']['auth_cacert']);
    curl_setopt($ch, CURLOPT_SSLCERT, $GLOBALS['config']['auth_cert']);
    curl_setopt($ch, CURLOPT_SSLKEY, $GLOBALS['config']['auth_key']);

    $response = curl_exec($ch);

    // logging errors and responses
    errorLog(true, $response, $ch);

    curl_close($ch);

    // in case an authentication request is executed
    if ($auth) {
        list($responseHeader, $responseBody) = explode("\r\n\r\n", $response, 2);

        $responseHeadersDigest = '';
        $responseHeadersSignature = '';
        // extracting digest and signature from headers
        $responseHeadersArray = explode("\r\n", $responseHeader);
        $responseProtocol = array_shift($responseHeadersArray);

        foreach ($responseHeadersArray as $value) {
            list($responseHeadersKeys, $responseHeadersValue) = explode(": ", $value, 2);
            $responseHeaders[strtolower($responseHeadersKeys)] = $responseHeadersValue;
        }

        $responseDigestAlgo = '';
        $responseDigestKey = '';
        foreach ($responseHeaders as $key => $value) {
            if ($key === "digest") {
                $responseHeadersDigest = $value;
                // stripping SHA algorithm for later comparison
                list($responseDigestAlgo, $responseDigestKey) = explode("=", $responseHeadersDigest, 2);
            } else if ($key === "signature")
                $responseHeadersSignature = $value;
        }

        // calculating response digest
        $responseDigest = base64_encode(hash(strtolower(str_replace("-", "", $responseDigestAlgo)), $responseBody, true));

        // checking digest validity
        if ($responseDigest !== $responseDigestKey) {
            errorLog(false, false, "Digests are not the same");
            die();
        }

        // extracting variables from signature header
        $signatureArray = explode(",", $responseHeadersSignature);
        foreach ($signatureArray as $value) {
            list($signatureKey, $signatureValue) = explode("=", $value, 2);
            $signature[$signatureKey] = trim($signatureValue, '"');
        }

        // generating siging string
        $signingStringArray = explode(" ", $signature['headers']);
        $signingString = '';
        foreach ($signingStringArray as $value) {
            $signingString = $signingString . trim($value) . ": " . trim($responseHeaders[$value]) . "\n";
        }

        // trimming last '\n' character
        $signingString = trim($signingString);

        // decoding signature
        $decodedSignature = base64_decode($signature['signature']);

        // verifying signature
        $signatureVerify = openssl_verify($signingString, $decodedSignature, openssl_get_publickey(file_get_contents($GLOBALS['config']['sign_cert_server'])), 'RSA-SHA256');

        if (!$signatureVerify) {
            errorLog(false, false, "Signature is not correct");
            while (($err = openssl_error_string()))
                errorLog(false, false, $err);
            die();
        }

        return json_decode($responseBody, true);
    }

    return $response;
}

/**
 * This function logs curl responses and errors in text files
 *
 * @param mixed $response the response
 * @param mixed $request  the request handle, used to get errors
 */
function errorLog($curl, $response, $request = false)
{
    $timestamp = '[' . date('Y-m-d H:i:s') . ']:';
    global $errorLog;
    global $responseLog;

    if ($response === false) {
        if ($curl)
            file_put_contents($errorLog, $timestamp . curl_error($request) . "\n", FILE_APPEND);
        else
            file_put_contents($errorLog, $timestamp . $request . "\n", FILE_APPEND);
    } else {
        file_put_contents($responseLog, $timestamp . "\n" . $response . "\n", FILE_APPEND);
    }
}

/**
 * This function generates the full signature header line from a set of headers, a certificate and the linked private key
 *
 * @param array  $headersToSign the headers to sign, in the $key => $value format
 * @param string $certificate   the certificate linked to the used private key
 * @param string $privateKey    the private key used to sign the headers
 *
 * @return string the full signature header line
 */
function generateSignatureHeader(array $headersToSign, string $certificate, string $privateKey): string
{
    // generating the signing string and header list
    $headers         = '';
    $signatureString = '';
    foreach ($headersToSign as $key => $value) {
        $normalizedHeaderKey = trim(strtolower($key));
        $headers             .= $normalizedHeaderKey . ' ';
        $signatureString     .= $normalizedHeaderKey . ': ' . trim($value) . "\n";
    }
    $headers         = trim($headers);
    $signatureString = trim($signatureString);

    // signing the signing string
    $signature = '';
    openssl_sign($signatureString, $signature, openssl_get_privatekey($privateKey), 'RSA-SHA256');
    $signature = base64_encode($signature);

    // compiling the header line
    return "keyId=\"$certificate\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"";
}

/**
 * This function deletes the access token from the session
 *
 * @param string $baseURL
 */
function logout(string $baseURL): void
{
    global $localTokenFile;
    file_put_contents($localTokenFile, '');

    header('Location: ' . $baseURL);
}

function displayMenuLoggedIn(): void
{
    global $replacementListItems;
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Logged In</h3>';

    echo '<form id="view_repos" method="post">';
    echo '<label for="report_id">Report ID: </label>';
    echo '<input type="number" id="report_id" name="report_id"><br><br>';
    foreach ($replacementListItems as $value) {
        echo '<label for="' . $value . '">' . $value . ': </label>';
        echo '<input type="text" id="' . $value . '" name="' . $value . '"><br><br>';
    }
    echo '<input type="hidden" id="action" name="action" value="view">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'view_repos\').submit(); return false;">View Repos</a></p>';
    echo '</form>';

    echo '<form id="logout" method="post">';
    echo '<input type="hidden" id="action" name="action" value="logout">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'logout\').submit(); return false;">Log Out</a></p>';
    echo '</form>';
}

function displayMenuLoggedOut(): void
{
    echo '<h2><a href="/">Home</a></h2>';
    echo '<h3>Not logged in</h3>';
    echo '<form id="login" method="post">';
    echo '<input type="hidden" id="action" name="action" value="login">';
    echo '<p><a href="javascript:{}" onclick="document.getElementById(\'login\').submit(); return false;">Log In</a></p>';
    echo '</form>';
}

// display client "home" page
if (!isset($_POST['action'])) {
    if (!empty($localToken['access_token'])) {
        displayMenuLoggedIn();
    } else {
        displayMenuLoggedOut();
    }
}

if (isset($_POST['action'])) {
    $replacementList = [];
    // Building replacement list
    foreach ($replacementListItems as $key => $value) {
        $replacementList[$value] = (isset($_POST[$value]) && strlen($_POST[$value]) > 0) ? $_POST[$value] : null;
    }
    // Building query array
    $repoQueryParameters = [
        'resource_type' => 'generic_report',
        'client_id' => $config['client_id'],
        'report_id' => (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0) ? $_POST['report_id'] : null,
        'replacementList' => $replacementList
    ];
    switch ($_POST['action']) {
        case 'login':
            $token = apiRequest(
                $config['token_uri'],
                [
                    'grant_type'    => 'client_credentials',
                    'client_id'     => $config['client_id'],
                    'client_secret' => empty($config['client_secret']) ? null : $config['client_secret'],
                    'scope'         => 'genericreports.readonly reports.readonly'
                ]
            );
            // We store the access token in the session so the user is "connected"
            // Only if the request has been successfully executed
            if (isset($token['access_token'])) {
                file_put_contents($localTokenFile, json_encode($token, true));

                // Redirecting the user's browser to the "home" page
                header('Location: ' . $baseURL);
            } else {
                echo $token;
            }
            break;
        case 'logout':
            logout($baseURL);
            break;
        case 'view':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );

            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);

            $resourceHeaders = explode("\r\n", $resourceHeader);
            $firstHeaderLine = array_shift($resourceHeaders);

            // Building form for download CSV button
            echo '<form method="post">';
            foreach ($_POST as $key => $value) {
                if ($key == "action") {
                    $value = "download";
                }
                echo '<input type="hidden" id="' . $key . '" name="' . $key . '" value="' . $value . '">';
            }
            if (isset($_POST['report_id']) && strlen($_POST['report_id']) > 0 && !isset(json_decode($resourceBody, true)['error'])) {
                echo '<button>Download as CSV</button>';
            }
            echo '</form>';

            // Displaying requested content
            echo '<pre>';
            echo (strpos($firstHeaderLine, "200")) ? json_decode($resourceBody) : $resourceBody;
            echo '</pre>';
            break;
        case 'download':
            // call to the resource server
            $resources = apiRequest(
                $config['resource_uri'],
                $repoQueryParameters,
                $localToken,
                false
            );

            // Separating headers from body
            list($resourceHeader, $resourceBody) = explode("\r\n\r\n", $resources, 2);
            $resourceHeaders = explode("\r\n", $resourceHeader);
            array_shift($resourceHeaders);

            // Dumping headers to insert them in current page
            foreach ($resourceHeaders as $key => $value) {
                header($value);
            }
            echo json_decode($resourceBody);
            break;
    }
}
Ce script a été conçu pour montrer le fonctionnement du mécanisme et tester l'implémentation d'un serveur, il n'a pas été testé extensivement. Il est conseillé d'utiliser une solution adaptée à un environnement de production.

Refresh Token

Refresh Token (ou jeton de rafraîchissement en français) est un mécanisme d'autorisation particulier. Il ne peut fonctionner en tant que tel, il fonctionne de pair avec Authorization Code. Lorsqu'un jeton d'accès arrive est arrivé en fin de vie, si le client y est autorisé, il peut faire une demande de renouvellement de son jeton d'accès auprès du serveur d'autorisation en présentant son jeton de rafraîchissement. Le serveur d'autorisation vérifie la validité et génère un autre jeton d'accès qu'il transmet au client, sans que l'utilisateur final n'aît à se connecter de nouveau.

Le principe de fonctionnement du rafraîchissement d'un jeton est le suivant :

  • Le jeton d'accès du client est arrivé à péremption. Le client effectue une demande de renouvellement en transmettant son Refresh Token au serveur d'autorisation.
  • Le serveur d'autorisation vérifie la validité des informations, "consomme" le jeton de rafraîchissement et génère une nouvelle paire de jetons
  • Le client reçoit sa nouvelle paire et utilise le nouveau jeton d'accès pour accéder aux ressources
   +------+                       +-------------+                  +-----------+
   |Client|                       |   Serveur   |                  |  Serveur  |
   +--+---+                       |Autorisation |                  | Ressources|
      |                           +------+------+                  +-----+-----+
      |        Authentification          |                               |
      |         + Refresh Token          |                               |
      +--------------------------------->|                               |
      |                                  |                               |
      |    Access (+ Refresh) token      |                               |
      |<---------------------------------+                               |
      |                                  |                               |
      |                      Requête vers|API                            |
      +----------------------------------+------------------------------>|
      |                                  |                               |
      |                                  |                               |
      |                                  |<------------------------------+
      |                                  |  Vérification d'autorisation  |
      |                                  +------------------------------>|
      |                                  |                               |
      |                         Données /|Réponse                        |
      |<---------------------------------+-------------------------------+
      |                                  |                               |

Demande de renouvellement d'un jeton

Pour obtenir un renouvellement de jeton d'accès, il faut effectuer la requête suivante : POST https://openflyers.com/nom-de-plateforme/oauth/access_token.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Les paramètres suivants sont nécessaires :

Nom Type Description
client_id string L'identifiant client_id reçu pendant l'enregistrement du client.
client_secret string La passphrase client_secret reçue pendant l'enregistrement du client.
grant_type string Le mécanisme d'autorisation utilisé. Ici, la valeur doit être "refresh_token" (sans guillements).
scope string (OPTIONNEL) Liste des droits demandés par le client, séparés par des espaces.

Si la requête est correcte, un jeton d'accès access_token est fourni en réponse dans un objet au format JSON.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU-G7fOBOYzOgioSGNIQKI2A2OxjsNBbOOoaHsqNpsQxWZse3rofExAaGKh2tXbeuz1YAVhdLUGYgq-oKRK4ONFhw2NvcRf3QPxQXZImLWw",
  "refresh_token": "a59ef39fa9bab9b95cd554f921e7f3080a34c90f23d2b8031d692b5ff2d0993dcc392d9c7f9a43242337ef144c1a5fe1d0174413ade973e1b628ac0bbfc39b23973534"
}

Liste des scopes disponibles

Un scope sur OAuth2 correspond à un droit d'accès sur une ressource particulière. Chaque scope est unique et indique de manière explicite le privilège qu'il donne. Il n'y a aucune restriction d'utilisation des scopes par rapport aux mécanismes. Chaque scope peut être utilisé avec n'importe quel mécanisme. Il n'y a que des recommandations vis à vis de leur utilisation. OpenFlyers définit une liste de scopes dédiée aux ressources accessibles par les clients. Ces scopes sont les suivants :

Nom Description
default.login Comportement par défaut lorsqu'aucun scope n'est précisé ou qu'un scope est invalide. Ce scope est recommandé pour Authorization Code.
genericreports.readonly Accéder aux rapports génériques autorisés pour le client en lecture seule. Ce scope est recommandé pour Client Credentials.
reports.readonly Accéder aux rapports personnalisés autorisés pour le client en lecture seule. Ce scope est recommandé pour Client Credentials.

Prolonger la durée de vie de la session du client de démonstration

La session du client de démonstration est initiée avec la méthode PHP session_start. Cette session peut être interrompue soit en cliquant sur le bouton de déconnexion, soit en fermant le navigateur (car les cookies associés à cette session se détruisent par défaut lorsque le navigateur est fermé).

Pour permettre à la session de rester active même après la fermeture du navigateur, il faut utiliser la méthode PHP session_set_cookie_params. Cette méthode donne la possibilité de définir une durée de vie pour les différents cookies associés à la session.

// Set the parameters for the session cookie in a PHP session in order to configure its lifetime in 30 days
$oauth2DemoSessionLifeTime = time() + (86400 * 30);
session_set_cookie_params($oauth2DemoSessionLifeTime);

NB: Cette approche n'est pas particulièrement sécurisée et peut présenter des risques de sécurité pour OpenFlyers. Par conséquent, elle doit être utilisée avec précaution.

Révocation de token

Pour initier la demande de révocation, rediriger le navigateur de l'utilisateur vers l'URL : POST https://openflyers.com/nom-de-plateforme/oauth/revoke.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée.

Envoyer également le paramètre suivant:

Nom Type Description
access_token string Le jeton d'accès (Chaîne de caractère unique permettant de prouver qu'un client OAuth a le droit d'accéder à une ressource.)
 apiRequest(
                $config['revoke_uri'],
                ['access_token' => $_SESSION['auth_token']['access_token']],
                null,
                false
            );

La demande de révocation se fait quand l'utilisateur clique sur le bouton Se déconnecter pour l'un des deux Clients Authorization Code et Client Credentials.

Si les jetons sont révoqués, l'utilisateur ne peut pas accéder à la plateforme OpenFlyers, il doit se reconnecter.

Utiliser l'API

Une fois un jeton d'accès obtenu, les données sont accessibles par une requête POST exécutée vers l'URL : https://openflyers.com/nom-de-plateforme/oauth/resources.php. Remplacer nom-de-plateforme par le nom de la plateforme utilisée. La requête POST doit respecter une structure particulière. Cette structure diffère en fonction du type de ressource souhaité et chaque ressource ne peut être accédée qu'avec le scope qui lui est associé. Le jeton d'accès doit être renseigné dans l'en-tête HTTP Authorization en y indiquant la valeur complète du jeton d'accès reçu précédé du type de jeton reçu  : Authorization: <token_type> <access_token>.

Récupérer les informations de l'utilisateur connecté

Ce type de ressource est nommé user_information. Cette ressource n'est accessible qu'avec le scope default.login. Cette ressource permet la récupération des informations de l'utilisateur connecté, en d'autre termes, l'utilisateur connecté lors de l'étape d'autorisation et correspondant à celui ayant émis la demande de jeton d'accès. À l'heure actuelle, seul l'identifiant de l'utilisateur connecté est retourné. La requête POST est construite de la manière suivante :

Nom Type Description
resource_type string Le type de ressource demandé, ici, ce champ correspond à user_information.
client_id string L'identifiant unique reçu pendant l'enregistrement du client.

Un exemple de requête complète au format JSON :

POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
           algorithm="rsa-sha256",
           headers="host content-type digest",
           signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=

{
    "resource_type":"user_information",
    "client_id":"d2615fe2020ec476"
}

La réponse est retournée dans un conteneur JSON.

Récupérer les rapports génériques

Ce type de ressource est nommé generic_report. Cette ressource n'est accessible qu'avec le scope genericreports.readonly. Cette ressource permet la récupération des rapports génériques au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :

Nom Type Description
resource_type string Le type de ressource demandé, ici, ce champ correspond à generic_report.
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
report_id int Identifiant du rapport souhaité.
replacementList array Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.

Un exemple de requête complète au format JSON :

POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
           algorithm="rsa-sha256",
           headers="host content-type digest",
           signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=

{
    "resource_type":"generic_report",
    "client_id":"d2615fe2020ec476",
    "report_id":135,
    "replacementList":{
        "year":2018
    }
}

La réponse est un fichier CSV retourné dans un conteneur JSON.

Le report_id se trouve dans la bibliothèque des rapports dans la 1ère colonne de la ligne du rapport concerné.

Pour identifier les paramètres à transmettre dans "replacementList", il faut retrouver le rapport dans la version anglaise de la documentation.

Exemple avec le rapport "Balances of resource accounts" : le chapitre indique que les paramètres/variables nécessaires sont accountingId et endDate.

Récupérer les rapports personnalisés

Ce type de ressource est nommé report. Cette ressource n'est accessible qu'avec le scope reports.readonly. Cette ressource permet la récupération des rapports personnalisés au format CSV autorisés pour le profil de l'utilisateur associé au client OAuth2 enregistré. La requête POST est construite de la manière suivante :

Nom Type Description
resource_type string Le type de ressource demandé, ici, ce champ correspond à report.
client_id string L'identifiant unique reçu pendant l'enregistrement du client.
report_id int Identifiant du rapport souhaité.
replacementList array Tableau correspondant aux différents paramètres modifiables pour générer le rapport souhaité.

Un exemple de requête complète au format JSON :

POST /nom-de-plateforme/oauth/resources.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: openflyers.com
Date: Wed, 04 Aug 2021 13:57:51 GMT
Accept: application/json
User-Agent: curl/7.64.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSU
Signature: keyId="58c450d937953829c8cca3613001f865a918da07",
           algorithm="rsa-sha256",
           headers="host content-type digest",
           signature="UsTjCPLsXzmo1b8FrA18SdLgaPamCpqR7tDHBhaiBnro6RbOkoD7="
Digest: SHA-256=Bi6/9qfJXEWme2/9o7VQMyvf+MED523bWdtdi91opwk=

{
    "resource_type":"report",
    "client_id":"d2615fe2020ec476",
    "report_id":1,
    "replacementList":{
        "year":2020
    }
}

La réponse est un fichier CSV retourné dans un conteneur JSON.

Procédures

Créer un client à partir du code source

Prérequis

Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php

Télécharger Le code source du client de démonstration

Le code source est mis à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip

Le code source est structuré de la manière suivante :

Error creating thumbnail: File missing
  • Le dossier css contient tout le matériel nécessaire à la stylisation de la page web.
  • Le dossier img contient les images affichées sur la page web.
  • Le dossier ssl est le dossier contenant tous les certificats et les clé privées associées à chaque client. Il doit respecter une structure particulière décrite ci-dessous.
  • Le fichier ClientDemo.php contient la classe ClientDemo. Cette classe contient toutes les méthodes nécessaires au fonctionnement du client de démonstration OAuth2.
  • Le fichier index.php est le fichier à appeler depuis le navigateur. Ce fichier correspond au fichier qui gère les appels à la classe ClientDemo et exécute les méthodes dans l'ordre.

Le dossier ssl doit respecter la structure suivante :

Oauth2 demo tree.png

Générer les certificats

Après la génération des certificats, les clés privées 'auth.key' et 'sign.key' remplacent celles présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo'.

Enregistrer les clients
  • Deux clients doivent être créés :
    • Le premier pour le mécanisme d'autorisation Authorization Code,
    • Le second pour le mécanisme d'autorisation Client Credentials.
Error creating thumbnail: File missing
  • Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton Télécharger le certificat CA de la page de gestion. Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton Télécharger le certificat de signature du serveur de la page de gestion. Placer les deux certificats téléchargés à la racine du dossier ssl.
  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Authorization Code et les placer dans le répertoire ssl/AuthCodeDemo.
  • Modifier le fichier ssl/AuthCodeDemo/config.authcode.json en le remplissant de la manière suivante :
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/AuthCodeDemo/auth.key",
  "sign_cert": "path_to_client/ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
  • Remplacer les XXXXXXXXXXXXXXXX des champs client_id et client_secret par les valeurs obtenues lors de l'enregistrement du client.
  • Remplacer mastructure par le nom de la structure sur laquelle la démo est testée.
  • Remplacer path_to_client/ par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
    • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/.
    • Exemple sur un serveur Linux debian: ./.


  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Client Credentials et les placer dans le répertoire ssl/ClientCredDemo.
  • Modifier le fichier ssl/ClientCredDemo/config.clientcred.json en le remplissant de la manière suivante :
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "path_to_client/ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "path_to_client/ssl/ClientCredDemo/auth.key",
  "sign_cert": "path_to_client/ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "path_to_client/ssl/ClientCredDemo/sign.key",
  "auth_cacert": "path_to_client/ssl/ca.crt",
  "sign_cert_server": "path_to_client/ssl/sign_cert_server.crt"
}
  • Remplacer les XXXXXXXXXXXXXXXX des champs client_id et client_secret par les valeurs obtenues lors de l'enregistrement du client.
  • Remplacer mastructure par le nom de la structure sur laquelle la démo est testée.
  • Remplacer path_to_client/ par le chemin d'accès vers le dossier qui contient le code source du client de démonstration.
    • Exemple sur windows: C:/wamp64/www/4.0/oauth-demo/.
    • Exemple sur un serveur Linux debian: ./.



NB: Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.

Enregistrer un client

Pour utiliser l'API OAuth2, il faut enregistrer un client OAuth2 auprès d'OpenFlyers. Pour ceci, suivre les étapes suivantes :


Pour le mécanisme d'authentification Client Credentials
  • Créer un nouveau profil. Ce profil doit permettre de gérer les droits du client OAuth2. Choisir un nom explicite, par exemple "Client OAuth rapports".
  • Sélectionner les droits à assigner à ce profil. Ces droits limitent les données auxquelles le client OAuth2 a accès.
    • Sélectionner les droits relatifs à l'enregistrement de clients OAuth2 dans l'onglet Admin (colonne Associé aux clients OAuth2).
    • Sélectionner les rapports accessibles par le profil précédemment créé.
  • Créer un nouvel utilisateur à partir du panneau de gestion. Cet utilisateur est virtuel et représente le serveur sur lequel fonctionne le client OAuth2.
    • Des identifiant et nom explicites sont recommandés (exemple : "serv1_oauth_client")
    • Attention : tout utilisateur désactivé et associé à un client OAuth2 rend le client inactif, il faut donc changer l'utilisateur associé au client pour réactiver le client.


Pour le mécanisme d'authentification Authorization Code
  • Aller dans Admin > Utilisateurs > Profils
  • Dans l'onglet Généralités, cocher la case relative à la colonne Connexion depuis l'extérieur (OAuth2) pour le profil souhaité.


Créer un nouveau client OAuth2
  • Aller dans Admin > Transferts > Exports > API OAuth2
Error creating thumbnail: File missing
  • Cliquer sur le bouton Ajouter + ou Ajouter un client
Error creating thumbnail: File missing
  • Choisir un nom pour le client.
  • Sélectionner le mécanisme d'autorisation utilisé par le client :
    • Authorization Code: permet d'utiliser OAuth2 comme solution SSO ou accéder à des données utilisateurs. Cette méthode peut être couplée avec le mécanisme de mémorisation de connexion (Refresh Token).
    • Client Credentials: permet d'utiliser OAuth2 dans un contexte d'automatisme.
  • Saisir l'URI de redirection vers le client pour le mécanisme Authorization Code.
  • Sélectionner l'utilisateur virtuel créé précédemment pour le mécanisme Client Credentials.
  • Générer deux CSR afin d'obtenir deux certificats signés et les saisir :
    • Certificate Signing Request pour le certificat d'authentification est utilisé pour l'authentification mutuelle avec mTLS (auth_cert.csr.pem).
    • Certificate Signing Request pour le certificat de signature est utilisé pour la signature des en-têtes HTTP (sign_cert.csr.pem).
Error creating thumbnail: File missing


  • Cliquer sur Enregistrer.

NB: La validité des certificats générés s'étend sur une période de 3 années.


Sauvegarder le couple ID/passphrase

Un couple ID/passphrase (client_id/client_secret) est généré. Ces deux clées ne sont communiquées qu'une seule fois. Elle doivent être stockées en toute sécurité et gardées confidentielles, et Mettre ces identifiants dans le fichier config.clientcred.json. Oauth2 client created.png

Error creating thumbnail: File missing


Télécharger les certificats crt

Les certificats signés sont téléchargeables depuis l'interface de gestion des clients OAuth2, les certificats sont disponibles dans les onglets Certificat d'authentification et Certificat de signature et les mettre dans le dossier /ssl du client OAuth2. Oauth2 client certificates.png

Téléchargez les certificats du serveur.
  • Les certificats du CA d'OpenFlyers et de signature HTTP du serveur sont nécessaires. Ils sont téléchargeables depuis l'interface de configuration des clients OAuth2.
Error creating thumbnail: File missing
  • Dans certains cas d'utilisation, il peut être nécessaire d'ajouter le certificat du CA d'OpenFlyers au Trust Store du système. Si cette étape n'est pas réalisée, les certificats peuvent être considérés comme invalides et peuvent ne pas être utilisables.
  • Pour ajouter le certificat CA au Trust Store du système, suivre les étapes suivantes:
    • Sous Linux, copier le certificat CA d'OpenFlyers dans le dossier /usr/local/share/ca-certificates et exécuter la commande sudo update-ca-certificates
    • Sous Windows,
      • Double-cliquer sur le certificat CA d'OpenFlyers téléchargé depuis l'interface d'enregistrement des clients OAuth2
      • Cliquer sur Installer un certificat...
      • Choisir l'emplacement de stockage (utilisateur ou ordinateur) et cliquer sur Suivant puis Suivant et enfin Terminer

Générer des certificats

L'API OAuth2 implémente HTTP Signature et l'authentification TLS mutuelle. Ces mécanismes utilisent chacun une paire certificat/clé privée différente.

Pour obtenir ces certificats, il faut d'abord générer des Certificate Signing Request (CSR).

La procédure est la suivante :

Fichier de configuration OpenSSL - sign_cert.conf
[req]
default_bits       = 4096                     # taille par défaut des nouvelles clés, peut être surchargé dans la commande
encrypt_key        = no                       # chiffrer la clé générée
distinguished_name = req_distinguished_name   # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                   # pointe vers la catégorie spécifiée pour les extensions x509
prompt             = no

[req_distinguished_name]
C                  =                          # code à deux chiffres du pays (ex: FR)
ST                 =                          # région/état (ex: Gironde)
L                  =                          # ville (ex: Bordeaux)
O                  =                          # organisation (ex: OpenFlyers)
OU                 =                          # unité organisationelle (ex: IT)
CN                 =                          # nom de domaine (ex: openflyers.com)

[v3_req]
keyUsage           = digitalSignature         # pour quelles opérations la clé peut-elle être utilisée
Fichier de configuration OpenSSL - auth_cert.conf
[req]
default_bits       = 4096                     # taille par défaut des nouvelles clés, peut être surchargé dans la commande
encrypt_key        = no                       # chiffrer la clé générée
distinguished_name = req_distinguished_name   # pointe vers la catégorie spécifiée pour le Distinguished Name
x509_extensions    = v3_req                   # pointe vers la catégorie spécifiée pour les extensions x509
prompt             = no

[req_distinguished_name]
C                  =                          # code à deux chiffres du pays (ex: FR)
ST                 =                          # région/état (ex: Gironde)
L                  =                          # ville (ex: Bordeaux)
O                  =                          # organisation (ex: OpenFlyers)
OU                 =                          # unité organisationelle (ex: IT)
CN                 =                          # nom de domaine (ex: openflyers.com)

[v3_req]
extendedKeyUsage   = clientAuth               # pour quelles opérations la clé peut-elle être utilisée


Exécuter les commandes suivantes :

  • <bash>openssl req -sha256 -newkey rsa -keyout sign.key -out sign_cert.csr.pem -outform PEM -config sign_cert.conf</bash>
  • <bash>openssl req -sha256 -newkey rsa -keyout auth.key -out auth_cert.csr.pem -outform PEM -config auth_cert.conf</bash>

Ces commandes prennent chacune en entrée le fichier de configuration et génèrent une clé privée et un Certificate Signing Request.

Une fois ces CSR obtenus :

  • Les renseigner dans les champs prévus à cet effet lors de la création d'un client et télécharger les certificats signés depuis l'interface une fois le client créé.
  • Garder la clé privée confidentielle. Une fuite poserait un risque de sécurité. Elle va de paire avec le certificat distribué par l'autorité de certification OpenFlyers.

Mettre en place une connexion à l'API OpenFlyers sur un serveur mutualisé

Note

La procédure ci-après est destinée à une mise en place lorsqu'il n'y a pas d'accès SSH en ligne de commande mais uniquement un accès FTP. Dans ce cas, la création des clés privées et publics est effectuée "en local". Dans la procédure suivante elle est effectuée depuis un PC sous Windows.

Prérequis
  • Posséder les accès FTP :
    • Hôte : XXXXXXXXXXXXXXXXXX
    • Login : XXXXXXXX
    • Mot de passe : XXXXXXXX
    • Port : XX (par exemple 21)
  • Télécharger Le code source du client de démonstration à disposition par OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/oauth2-demo-src.zip
Procédure
  • Générer les certificats en local.
  • Remplacer les clés privées 'auth.key' et 'sign.key' présentes dans les dossiers 'ssl/AuthCodeDemo' et 'ssl/ClientCredDemo' par les clés générées.
  • Enregistrer les deux clients :
    • Le premier pour le mécanisme d'autorisation Authorization Code.
    • Le second pour le mécanisme d'autorisation Client Credentials.
Error creating thumbnail: File missing
  • Télécharger le certificat du CA OpenFlyers en cliquant sur le bouton Télécharger le certificat CA de la page de gestion.
  • Télécharger aussi le certificat de signature du serveur en cliquant sur le bouton Télécharger le certificat de signature du serveur de la page de gestion.
  • Placer les deux certificats téléchargés à la racine du dossier ssl.
  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Authorization Code et les placer dans le répertoire ssl/AuthCodeDemo.
  • Modifier le fichier ssl/AuthCodeDemo/config.authcode.json en le remplissant de la manière suivante.
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "authorize_uri": "https://openflyers.com/mastructure/oauth/authorize.php",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/AuthCodeDemo/auth_cert.crt",
  "auth_key": "./ssl/AuthCodeDemo/auth.key",
  "sign_cert": "./ssl/AuthCodeDemo/sign_cert.crt",
  "sign_key": "./ssl/AuthCodeDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}


  • Télécharger les deux certificats Certificat d'authentification et Certificat de signature du client Client Credentials et les placer dans le répertoire ssl/ClientCredDemo.
  • Modifier le fichier ssl/ClientCredDemo/config.clientcred.json en le remplissant de la manière suivante.
{
  "client_id": "XXXXXXXXXXXXXXXX",
  "client_secret": "XXXXXXXXXXXXXXXX",
  "token_uri": "https://openflyers.com/mastructure/oauth/access_token.php",
  "resource_uri": "https://openflyers.com/mastructure/oauth/resources.php",
  "revoke_uri": "https://openflyers.com/mastructure/oauth/revoke.php",
  "auth_cert": "./ssl/ClientCredDemo/auth_cert.crt",
  "auth_key": "./ssl/ClientCredDemo/auth.key",
  "sign_cert": "./ssl/ClientCredDemo/sign_cert.crt",
  "sign_key": "./ssl/ClientCredDemo/sign.key",
  "auth_cacert": "./ssl/ca.crt",
  "sign_cert_server": "./ssl/sign_cert_server.crt"
}
  • Remplacer les XXXXXXXXXXXXXXXX des champs client_id et client_secret par les valeurs obtenues lors de l'enregistrement du client.
  • Remplacer mastructure par le nom de la structure sur laquelle la démo est testée.
  • Transférer le fichier "oauth-demo" vers le serveur mutualisé :
    • Télécharger FileZilla.
    • Lancer FileZilla.
    • Entrer l'URl du serveur mutualisé dans le champ Hôte.
    • Entrer le login dans le champ Nom d'utilisateur.
    • Entrer le mot de passe dans le champ Mot de passe.
    • Entrer le port dans le champ Port.
    • Cliquer sur le bouton Connexion.
    • Accéder à l'emplacement du répertoire "oauth-demo" en local à gauche dans l'onglet "Site local".
    • Choisir l'emplacement où placer le répértoire oauth-demo dans l'anglet Site distant à droite.
    • Glisser et déposer le oauth-demo à l'emplacement choisi.
Error creating thumbnail: File missing
  • Accéder au client OAuth-demo depuis le serveur mutualisé en utilisant l'URL du domaine du serveur : url_de_domaine_de_serveur/oauth-demo/index.php
  • Modifier la valeur URI de redirection vers le client du client AuthCodeDemo précédemment créé en remplaçant l'ancienne URL par la nouvelle.


NB: Le certificat de signature du serveur est unique à chaque plateforme et serveur. Ainsi, si le serveur ou la plateforme est modifié, le certificat doit être renouvelé.

Récupérer les données d'un utilisateur

  • Cliquer sur le bouton Récupérer les informations utilisateur, l'identifiant de l'utilisateur s'affiche.
  • Utiliser l'identifiant récupérer afin de récupérer toute information associée à cet utilisateur en créant de nouveaux rapports personnalisés

Utiliser le client

Prérequis

Un client de démonstration est disponible pour le logiciel OpenFlyers à cette adresse : https://openflyers.com/oauth2-demo/index.php

Le client de démonstration se présente de la manière suivante.

Oauth2 client demo.png

La démonstration est composée de deux colonnes. La première, nommée Authorization Code correspond au mécanisme d'autorisation du même nom. Elle dispose d'un bouton permettant de se connecter ainsi que d'une section indiquant les informations relatives à l'état de la connexion. La seconde colonne, nommée Client Credentials correspond elle aussi au mécanisme d'autorisation du même nom. Comme pour la première colonne, les éléments qui y sont présentés sont identiques. La différence étant que le bouton de connexion n'a pas le même effet étant donné que ces deux mécanismes sont différents. Chaque mécanisme est indépendant et il est possible de se connecter à un des deux mécanismes sans se connecter à l'autre ou se connecter aux deux en même temps.

Le mécanisme Authorization Code
  • Cliquer sur le bouton Se connecter (ce qui redirige le navigateur vers la page de connexion du logiciel OpenFlyers).
  • Renseigner les identifiants de l'administrateur pour s'y connecter.
  • Nom d'utilisateur : admini.
  • Mot de passe : azerty.

Une fois les informations saisies, la page suivante est affichée.

Oauth authorize demo.png

  • Cliquer sur le bouton Autoriser l'application pour autoriser la connexion (ce qui se redirige le navigateur vers la page du client de démonstration OAuth2).

La première colonne doit afficher l'état de connexion Connecté ainsi qu'un nouveau bouton Récupèrer les informations utilisateurs qui permet de récupérer les informations de l'utilisateur connecté.

Le mécanisme Client Credentials
  • Cliquer sur le bouton de connexion Se connecter: contrairement à celui du mécanisme Authorization Code, ne redirige pas le navigateur vers la page de connexion du logiciel OpenFlyers. Le bouton de connexion utilise les identifiants du client, ici le couple clé privée/clé publique, pour initier la connexion avec le serveur d'autorisation et obtenir un jeton d'accès.

Une fois la connexion établie, la seconde colonne doit afficher l'état de connexion Connecté ainsi qu'un menu déroulant Rapport à récupèrer et un nouveau bouton Récupèrer le rapport qui permet de récupérer les rapports génériques et personnalisés.


Le client, une fois connecté sur les deux mécanismes, se présente de la manière suivante.

Oauth2 connected demo.png

Troubleshooting

500 Internal Server Error en récupérant le rapport

La démo utilise les valeurs par défaut pour extraire les rapports. Une erreur 500 indique une "Erreur de syntaxe ou violation d'accès" lors de l'exécution de la requête du rapport. Cela se produit parce que le rapport n'a pas de valeurs par défaut associées, étant donné qu'il n'a jamais été visualisé dans l'interface web. Pour résoudre ce problème, il vous suffit de visualiser le rapport et de cocher la case "Mémoriser ce choix".

Erreur "File not found."

Cette erreur se produit lorsque l'URI utilisé n'existe pas sur le serveur OpenFlyers. Vérifier les URIs mis en place dans les fichiers de configuration et essayer de nouveau.

int_rsa_verify : longueur de signature incorrecte

Ce problème pourrait survenir si les fichiers ca.cert et sign_cert_server.cert ne proviennent pas du même serveur que celui du client oauth2. La solution est :

Si cela ne fonctionne pas