Accueil > OpenID Connect OAuth Server par DnC > Développer > OpenID Connect > API OpenID Connect : Points d’extrémité

API OpenID Connect : Points d’extrémité

  publié le par DnC

Les points d’extrémité d’OpenID Connect jouent un rôle semblable à ceux du protocole OAuth 2.0 mais sont plus complets.

Points d’extrémité définis par OpenID Connect

Le point d’extrémité d’autorisation et le point d’extrémité de jeton sont tous deux situés sur le serveur d’autorisation (ce serveur).

Le point d’extrémité de redirection est situé dans l’application cliente. Il revient donc à l’administrateur de cette application de développer le code du point d’extrémité de redirection.

La spécification d’OpenID Connect ne décrit pas comment l’URI de ces points d’extrémité est définie. C’est à chaque développeur de décider. Cependant, l’observation de différents serveurs permet de dégager des options courantes, que nous avons adoptées.

Notons que OpenID Connect prévoit la découverte des points d’extrémités, ce que OAuthSD implémente à cette URL : [http://oa.dnc.global/.well-known/openid-configuration]

Voici comment OAuthSD définit les points d’extrémité du protocole OpenID Connect :

Point d’extrémité d’autorisation (Authorization Endpoint)
https://oa.dnc.global/authorize
Usage dans le cadre du flux Autorisation via un code : OpenID Connect : Obtenir une autorisation pour l’application cliente

Le point d’extrémité d’autorisation est le point d’extrémité sur le serveur d’autorisation auquel l’agent de l’utilisateur final (user-agent : le navigateur ou l’application mobile etc.) est redirigé par l’application cliente, pour permettre à l’utilisateur final de s’authentifier et d’accorder les autorisations demandées par l’application cliente.

Lors du traitement de la demande d’authentification, le serveur crée un jeton d’accès et un jeton d’identification. Ces jetons seront associés à l’ID de l’utilisateur final, ce qui ouvre une notion de session.

Dans le cas du flux d’autorisation via un code (celui qui nous intéresse le plus), à ce stade, il n’est retourné qu’un code d’autorisation de durée de vie très courte [1]. Les jetons ne seront retournés qu’en réponse à l’appel du point d’extrémité token, dans une liaison serveur-serveur, ce qui fonde la sécurité de ce flux.

Dans le cas des flux implicites et hybrides, utilisés pour des applications sans serveur (back-end), les jetons sont directement retournés dans la réponse et sont donc visibles par l’agent de l’utilisateur, ce qui est considéré comme une faiblesse de ces flux.

Point d’extrémité de redirection, ou URI de retour à l’application cliente (Redirection Endpoint)
Le point d’extrémité de redirection est le point d’extrémité dans l’application cliente vers lequel l’agent de l’utilisateur est redirigé, après avoir obtenu l’autorisation au point d’extrémité d’autorisation. Cet URI est défini par l’administrateur d’une application cliente quand il l’inscrit sur ce serveur. Voir : OpenID Connect : Lier une application cliente au serveur OAuthSD. Ce point d’extrémité pourrait être distinct de celui du protocole OAuth 2.0 car OpenID Connect prévoit des échanges différents et requiert que le jeton obtenu soit vérifié par l’application cliente. Cet article explique comment faire : OpenID Connect : Exemples complets du flux d’Autorisation via un code puis requête UserInfo

Point d’extrémité de jeton (Token Endpoint)
https://oa.dnc.global/token
Usage dans le cadre du flux Autorisation via un code : OpenID Connect : Obtenir les jetons d’accès

Le point d’extrémité de jeton est le point d’extrémité sur le serveur d’autorisation auquel s’adresse l’application cliente, avec le code d’autorisation et l’ID client, pour obtenir un jeton d’accès et un jeton d’identité ou rafraîchir des jetons ayant expiré.

Le jeton d’accès est utilisé pour effectuer des appels authentifiés vers une API sécurisée, tandis que le jeton d’identification contient des attributs de profil utilisateur représentés sous la forme de déclarations (claims). Les deux JWT ont une date d’expiration indiquée par la revendication exp (parmi d’autres mesures de sécurité, comme la signature).

Point d’extrémité d’introspection (Introspection Endpoint)
https://oa.dnc.global/introspect
Usage : Demande d’introspection.

La "norme" OpenID Connect ne définit pas l’introspection. En effet, dans une approche simpliste, OpenID Connect ne protège que les données UserInfo qui, étant situées sur le même serveur d’authentification, ne nécessitent pas l’introspection.
Mais si on souhaite utiliser OpenID Connect pour accéder à des données protégées situées sur un serveur de ressource distinct du serveur d’authentification, l’introspection est une des deux méthodes applicables pour permettre à ce serveur de valider le jeton JWT. La problématique générale est exposée ici : Validation du jeton d’accès par une ressource protégée.

Notons que l’introspection n’est pas une particularité d’OAuthSD, d’autres implémentations offrant également cette fonctionnalité.

Point d’extrémité d’informations sur l’utilisateur final (UserInfo)
https://oa.dnc.global/userinfo
Pour réaliser l’authentification, le protocole OpenID Connect permet à une application cliente d’accéder à l’identité de l’utilisateur final ayant accordé l’autorisation, ainsi qu’à différentes informations selon le scope passé dans la requête.
Usage :
- Demande d’informations sur l’utilisateur (UserInfo Endpoint),
- Réponse UserInfo.
Références :
- UserInfo Endpoint

Point d’extrémité d’informations sur les clefs (Keys Endpoint)
Ce point permet à un serveur de ressource d’accéder à la clé publique qui lui permettra de valider le jeton d’identité, comme exposé dans cet article : Validation du jeton d’accès par une ressource protégée.

https://oa.dnc.global/keys

Ce sujet est traité plus en détail ici : API OpenId Connect : Point d’extrémité d’informations sur les clefs (Keys Endpoint)

Point d’extrémité de déconnexion (Logout Endpoint)

https://oa.dnc.global/logout.php

Ce point permet la déconnexion de l’utilisateur au niveau du serveur de toutes ses sessions sécurisées. Si un cookie SLI avait été généré, il est détruit, interdisant ainsi le rafraîchissement automatique des sessions de l’utilisateur ouvertes à partir d’autres applications.
L’appel de Logout se fait exactement comme Introspect, avec un jeton d’identité valide.

Découverte des points d’extrémité
Conformément à la spécification OpenID Connect Discovery 1.0, OAuthSD expose ses métadonnées à l’URI :

https://oa.dnc.global/.well-known/openid-configuration

Ce sujet est traité plus en détail ici : API OpenID Connect : Découverte.

Notes

[1Nous prévoyons de le rendre à usage unique. Il ne semble pas que la norme le prévoit, est-ce convenable ?

API OpenID Connect : Point d’extrémité d’autorisation (Authorization Endpoint)

  publié le par DnC

C’est le point d’extrémité sur le serveur d’autorisation auquel l’user-agent (en général un navigateur Web) de l’utilisateur final est redirigé, pour lui permettre de s’identifier et d’accorder des autorisations à l’application cliente.
Dans le cas du flux d’autorisation avec code (Authorization code flow), le contrôleur Authorize redirige l’user-agent sur le point d’extrémité Token avec un code d’autorisation.
Le flux implicite permet d’obtenir directement les jetons.

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

https://oa.dnc.global/authorize

Forme de la demande d’autorisation

Exemples d’appel Authorize :

PHP

  1.     $data = array(
  2.         'response_type' => 'code',
  3.         'client_id' => 'chemin_openid',
  4.         'state' =>  $oauth_state,
  5.         'scope' => 'openid profile',
  6.     );
  7.  
  8. $authorization_endpoint = 'https://oa.dnc.global/authorization';
  9.  
  10. $authorization_endpoint .= '?' . http_build_query($data);
  11.     header('Location: ' . $authorization_endpoint);
  12.     exit();

Télécharger

SPIP

  1.     include_spip('inc/headers');
  2.    
  3.     $oauth_state = session_get('oauth_state');
  4.     $url = "http://oa.dnc.global/authorize?response_type=code&client_id=chemin_openid&scope=openid profile&redirect_uri=http://chemindeleau.com/callback_openid.php&state=$oauth_state";
  5.    
  6. redirige_par_entete($url);

Télécharger

Notes :
- Pour obtenir un jeton d’identité, le scope doit comporter "openid". Dans le cas contraire, la réponse sera identique à celle du protocole OAuth 2.0, et ne comprendra donc que le jeton d’accès.
- Pour obtenir un jeton de rafraîchissement (Refresh Token), le scope doit comporter "offline_access".
- Bien que la "norme" indique que le paramètre redirect_uri est obligatoire, il peut être omis si l’application cliente a été inscrite avec une seule adresse de retour.
- si l’application cliente a été inscrite avec plusieurs adresses de retour, le paramètre redirect_uri est obligatoire, et doit représenter l’une d’elles.
- Il est possible de rajouter à l’URL tout paramètre utile, comme un identificateur de session. Ceux-ci seront retransmis dans le corps de la réponse, de façon quasi intégrale.
- Avant qu’elle puisse interagir dans un flux OpenID Connect, l’auteur doit inscrire l’application cliente sur le serveur OAuthSD avec les paramètres attendus par OpenID Connect.
- Il est de la responsabilité de l’application cliente d’assurer la bonne forme et la sécurité des valeurs transmises par les paramètres d’URL.

Authentification de l’utilisateur final

A l’appel du Point d’extrémité d’autorisation :
- le serveur OAuthSD redirige l’user-agent vers la page d’authentification (on reste dans le domaine du serveur d’autorisation).
- l’utilisateur final s’authentifie dans cette page (les identifiants sont donc confinés au niveau du serveur).
- le serveur poste le code d’autorisation au Point d’extrémité de redirection.

Voici un exemple de page d’authentification :

http://oa.dnc.global/authorize?response_type=code&client_id=chemin_openid&redirect_uri=http://chemindeleau.com/callback_openid.php&state=xyz

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

Retour à l’application cliente

En cas de succès, le serveur redirige le navigateur sur le point d’extrémité de redirection dans l’application cliente ( par entête HTTP code 302 ). Cet URI est défini par l’auteur d’une application cliente quand il l’inscrit sur ce serveur. Voir : OpenID Connect : Lier une application cliente au serveur OAuthSD.

Les paramètres code et state sont passés dans l’URL. Exemple :

http://chemindeleau.com/callback_openid.php?code=3159339c2f1326f9fa128e161b8387feca690b65&state=98b3027139f7cb3be4a885d7c81b41bb

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

Situations d’erreur

En cas d’échec le corps de la réponse contient :

index type valeur
page JSON Array error : titre de l’erreur,
error_description : description de l’erreur

La réponse HTTP ainsi que les valeurs de error et error_description sont données par le tableau suivant :

Réponse error
titre de l’erreur
error_description
description de l’erreur
Explication
302 login required The user must log in Le jeton d’accès n’existe pas ou n’est plus valide. Cette réponse n’apparait que si login=’none’ car, dans les autres cas, le serveur OAuthSD prend à sa charge le dialogue de connexion. Cette erreur pourra apparaître en cas d’échecs successifs.
302 interaction_required The user must grant access to your application Le serveur a déterminé que des scopes doivent être approuvés par l’utilisateur final. Il y a peu de chance que cette erreur apparaisse avec OAuthSD, car la situation est gérée au niveau du contrôleur Authorize qui prend à sa charge le formulaire d’acceptation.
302 consent_required The user denied access to your application L’utilisateur final a explicitement refusé la demande d’autorisation qui lui a été présentée.
400 invalid_client No client id supplied Le paramètre client_id est obligatoire.
400 invalid_client The client id supplied is invalid Le paramètre client_id doit être identique à ce qui a été inscrit pour l’application.
400 invalid_uri The redirect URI must not contain a fragment
400 invalid_uri No redirect URI was supplied or stored
400 invalid_uri A redirect URI must be supplied when multiple redirect URIs are registered
400 redirect_uri_mismatch The redirect URI provided is missing or does not match Le paramètre redirect_uri doit être identique à ce qui a été inscrit pour l’application.
400 invalid_uri The redirect URI must not contain a fragment
401 not_allowed L’utilisateur final a probablement fait une erreur dans ses identifiants. Recommencer.
401 malformed_identifier L’utilisateur final a fourni un identifiant (e-mail ou pseudo) mal formé. Recommencer.
401 malformed_email Un E-mail est requis en guise d’identifiant de l’utilisateur final. L’utilisateur final a probablement fait une erreur. Recommencer.
403 consent_required The user denied access to your application L’utilisateur a refusé son accord ou a abandonné l’authentification.
403 forbidden Probablement une attaque de type "Man in the middle". Ne pas réagir.

Si le serveur retourne une erreur 401, il est de la responsabilité de l’application cliente de présenter un message d’erreur à l’utilisateur final et de recommencer (ou non) l’authentification. Dans tous les autres cas d’erreur, l’application cliente ne devrait pas redemander l’authentification avant d’avoir identifié l’erreur au niveau technique.

Dans certains cas, la redirection peut être effectuée, le corps de la réponse contenant un message d’erreur :

error
titre de l’erreur
error_description
description de l’erreur
Explication
invalid_request Invalid or missing response type response_type doit être "code"
unauthorized_client The grant type is unauthorized for this client_id Vérifiez que l’application a bien été inscrite avec le type d’autorisation Authorization_code
redirect_uri_mismatch The redirect URI is mandatory and was not supplied Le paramètre redirect_uri doit figurer dans la requête.

Réponse sans redirection

Un certain nombre d’erreurs provoquent une réponse HTTP directe (sans rediriger le navigateur sur le point d’extrémité de redirection). Il s’agit d’anomalies dans la requête, traduisant une tentative d’intrusion. Le code d’erreur HTTP est le plus souvent 400 ’Bad Request’ ou 403 ’Forbidden’.
Ce type d’erreur ne devrait jamais se produire avec une application cliente autorisée. Le concepteur d’une application cliente n’a pas à s’en préoccuper.

API OpenID Connect : Point d’extrémité de jeton (Token Endpoint)

  publié le par DnC

Pour obtenir les jetons, une application cliente s’adresse au point d’extrémité token avec le code obtenu dans la phase d’autorisation. Dans le cadre du protocole OpenID Connect, l’appel au contrôleur Token suit la demande d’authentification.

Notons que OAuth 2.0 définit des flux effectuant un appel direct au contrôleur Token, voir : différents type de flux. Notons également que le flux implicite défini dans le cadre d’OpenID Connect obtient directement les jetons du contrôleur Authorize.

Point d’extrémité de jeton (Token Endpoint)

https://oa.dnc.global/token

Le point d’extrémité de jeton est le point d’extrémité sur le serveur d’autorisation auquel s’adresse l’application cliente avec le code d’autorisation.

Forme de la demande de jeton d’accès

La demande ne doit être effectuée que par la méthode POST.

Pour l’authentification de l’application cliente auprès du serveur d’autorisation, OAuthSD impose la méthode client_secret_basic. L’authentification est donc effectuée en utilisant l’authentification HTTP Basic (cf. section 2.3.1 de OAuth 2.0 [RFC6749]). Les identifiants client_id et client_secret sont ceux qui ont été définis lors de l’inscription de l’application cliente sur le serveur.

Les paramètres suivants doivent être postés :
- grant_type : Type de flux d’autorisation, doit être "authorization_code".
- code : le code d’autorisation reçu.
- redirect_uri : l’adresse de retour à l’application cliente.

Réponse du serveur

En cas de succès, le serveur retourne une réponse HTTP 200. Le corps de la réponse contient :

index type valeur
page JSON array access_token : (string) jeton d’accès OAuth 2.0
expires_in : (long) durée de vie en secondes
token_type : (string) "Bearer"
scope : (string) "openid ... "
id_token : (string) jeton d’identification (JWT)

Le Header comporte, comme il se doit, la directive ’Cache-Control : no-cache, no-store’.

 

En cas d’échec, le corps de la réponse contient :

index type valeur
page JSON Array error : titre de l’erreur,
error_description : description de l’erreur

La réponse HTTP ainsi que les valeurs de error et error_description sont données par le tableau suivant :

Réponse error
titre de l’erreur
error_description
description de l’erreur
Explication
405 invalid_request The request method must be POST when requesting an access token La méthode de la demande doit être POST lorsque vous demandez un jeton d’accès. Notez que le Header contient ’Allow : POST’
400 invalid_request The grant type was not specified in the request Le type d’autorisation n’a pas été spécifié dans la demande
400 unsupported_grant_type Grant type "X" not supported Le Type d’autorisation "X" n’est pas supporté.
400 invalid_grant Authorization code doesn\’t exist or is invalid for the client Le code d’autorisation n’existe pas ou est invalide pour le client. Cette situation peut résulter du fait que le code a déjà été utilisé pour une demande de jeton.
400 unauthorized_client The grant type is unauthorized for this client_id Le type d’autorisation n’est pas autorisé pour ce client_id
400 invalid_scope The scope requested is invalid for this request La portée d’autorisation demandée est invalide pour cette demande
400 invalid_scope The scope requested is invalid for this client La portée d’autorisation demandée est valide pour ce client
400 invalid_scope An unsupported scope was requested La portée d’autorisation demandée n’est pas supportée

Demande du jeton de rafraîchissement

OpenID Connect ne retourne un jeton de rafraîchissement (Refresh Token), conjointement au jeton d’accès, que si le scope "offline_access" a été inclus dans la requête et accepté, ce qui ne se produira qu’avec le flux d’Autorisation via un code (Authorization Code Grant).

API Open ID Connect : Introspection

  publié le par DnC

L’introspection permet à un serveur de ressource (RS) étranger à l’organisation gérant le serveur d’authentification (AS) de valider un jeton d’identité (ID Token).

Cela permet également de rafraîchir les informations sur le processus d’authentification à l’origine de la création du jeton.

Avantages et inconvénients de l’Introspection

La validation du jeton d’identité (ID Token) auprès du serveur d’authentification (introspection) présente quatre avantages importants :
- cette méthode ne nécessite pas de connaître la clé publique de l’application cliente pour valider la signature du jeton, ce qui permet à des serveurs de ressource étrangers à l’organisation de valider les jetons reçus ;
- elle permet de savoir si le jeton a été révoqué ;
- on peut obtenir des informations sur l’utilisateur final (qui est à l’origine de l’autorisation) qui permettent d’identifier cet utilisateur et, donc, d’agir en fonction de la confiance à accorder d’après son profil ;
- il est possible de mettre en œuvre la déclaration "jti" (JWT ID) qui permet au serveur d’autorisation de vérifier que le jeton n’a pas déjà été utilisé ;
- OAuthSD propose également la vérification de l’IP du demandeur : si une ressource protégée a transmis l’adresse IP de son propre demandeur avec le paramètre ’requester_ip’, on vérifie que cette adresse IP se trouve dans le sous-réseau de l’application cliente identifiée par la déclaration ’aud’. Cela est éeesntiel pour ne pas répondre à un malware ayant intercepté le jeton.

Elle a cependant l’inconvénient d’augmenter le trafic avec le serveur d’autorisation.

Remarque quant au lien entre jeton d’identité et jeton d’accès et l’intérêt réel de ce dernier

Justin Richer : (openid-specs-ab)

"Il n’y a pas de relation 1:1 entre le jeton d’accès et jeton d’identité (ID Token), surtout si vous considérez que le jeton d’accès peut être actualisé ou ne pas expirer, alors que le jeton d’identité doit expirer. Le ID Token représente l’événement authn (et la session, dans une certaine mesure), alors que le jeton d’accès représente une autorisation déléguée d’accès aux informations de profil de l’utilisateur. Vous pouvez obtenir des informations sur l’événement d’authentification qui a généré le jeton d’accès à partir de l’introspection, mais il est important de se rappeler que le jeton d’accès n’est pas destiné à être étroitement lié à cet événement d’authentification. En fait, c’est toute la question d’OAuth qui lie de façon lâche l’authentification au jeton d’accès et maintient le jeton d’accès valide alors que l’utilisateur n’est plus là.

Cette remarque à propos de la faiblesse du lien entre jeton d’accès et jeton d’identité rejoint la conclusion émise à propos de la requête Userinfo : toute requête Userinfo devrait suivre le cycle : demande d’autorisation, validation du ID Token, demande Userinfo, vérification de la concordance des user_id. C’est ce qui est décrit dans les exemples.

Que vaut réellement un jeton d’accès si :
- on ne peut vérifier sa validité sans introspection [1] (autant alors faire l’introspection pour le jeton d’identité),
- on n’est pas certain du lien avec le jeton d’identité ?

Plus généralement, ne devrait-on pas conclure ainsi : plutôt que d’utiliser le jeton d’accès ne vaudrait-il pas mieux utiliser exclusivement le jeton d’identité ? Notons qu’il existe un flux OpenID Connect ne retournant que l’ID Token.

Implémentation de l’Introspection

Il n’y a pas (à ce jour) de "norme" définissant l’Introspection pour OpenID Connect. Cependant, OAuthSD, ainsi que les implémentations courantes, se fonde sur la proposition de standard RFC 7662 : OAuth 2.0 Token Introspection. DnC a proposé une fonction d’introspection pour la bibliothèque OAuth 2.0 PHP.

Forme de la demande d’Introspection

Point d’extrémité d’introspection (Introspection Endpoint)

https://oa.dnc.global/introspect

Le jeton est passé avec le paramètre "token" par l’une des méthodes suivantes : Auth Header ou POST (GET est possible mais non sécurisé).

Une seule méthode doit être utilisée à la fois, sinon le serveur retourne ’400, invalid_request’.

Méthode Auth Header :
C’est la méthode que nous recommandons.

Méthodes POST :
Lorsque vous placez le jeton dans le corps de la requête HTTP, la méthode doit être POST ou PUT. Sinon, le serveur retourne 400, ’invalid_request’, ’When putting the token in the body, the method must be POST or PUT’, ’#section-2.2’.

Le type de contenu pour les requêtes POST doit être "application / x-www-form-urlencoded. Si ce n’est pas le cas, le serveur retourne 400, ’invalid_request’, ’The content type for POST requests must be "application/x-www-form-urlencoded".

Méthodes GET :
Cette méthode n’est pas recommandée pour des raisons de sécurité.

Réponse du serveur

En cas de succès, le serveur retourne une réponse HTTP 200.

Le corps de la réponse contient un tableau portant les informations suivantes :

index type valeur
status entier code HTTP
headers string Headers de la réponse
page string JSON Array :
active : true,
scope : (JSON string Array) scopes associés au jeton.
client_id : ID de l’application cliente qui a demandé ce jeton.
username : ID OAuth de l’utilisateur final (human-readable).
exp : (long) (secondes depuis le 1° janvier 1970). Unix Time de la fin de validité du jeton.
iat : (long) (secondes depuis le 1° janvier 1970). Unix Time de création du jeton
iss : (string) issuer : serveur d’authentification qui a diffusé ce jeton.
sub : identifiant interne de l’utilisateur qui a autorisé ce jeton.
aud : audience définie pour ce jeton.

 

Si le jeton n’est pas valide, alors que la requête n’a pas échoué, l’introspection ne retourne un code HTTP 200 et une réponse active : false.

Note :
- Le traitement des erreurs d’introspection décrit dans ce qui suit est propre à cette version de OAuthSD, destinée au développement et à la mise au point d’une application mettant en oeuvre la délégation d’authentification. Conformément à la spécification, les serveurs OAuthSD de production ne donnent pas de détail sur l’erreur, mais retournent simplement un code HTTP 200 et une réponse active : false.

En cas d’échec de la requête, le corps de la réponse contient :

index type valeur
page string JSON Array :
error : titre de l’erreur,
error_description : description de l’erreur

La réponse HTTP ainsi que les valeurs de error et error_description sont données par le tableau suivant :

Réponse error
titre de l’erreur
error_description
description de l’erreur
Explication
400 invalid_request Only one method may be used to authenticate at a time (Auth header, GET or POST) La requête est mal formée
400 invalid_request Missing parameters : "token" is required La requête Introspection requiert le paramètre ’token’.
400 invalid_request When putting the token in the body, the method must be POST or PUT Si on place le token dans le corps de la requête, la méthode ne peut être que POST ou PUT
400 invalid_request The content type for POST requests must be "application/x-www-form-urlencoded l’IETF spécifie ce type de contenu. NB : tous les serveurs Web ne remplissent pas cette variable _SERVER voir http://tools.ietf.org/html/rfc6750#section-2.2
401 invalid_token JWT is malformed le jeton JWT ne peut être décodé.

Exemples

Demande de validation d’un jeton d’identité, méthode Auth Header (ou "JWT Bearer" ) :

PHP

  1. /*
  2. Autorisation avec OAuth Server by DnC
  3. OpenID Connect : Introspection, méthode Auth Header
  4. */
  5. function oauth_authorize($idtoken) {
  6.  
  7.     $Ok = false;
  8.  
  9.     if ( !empty( $idtoken) ) {
  10.  
  11.         $h = curl_init(AUTHENTICATION_SERVER_URL . 'introspect');
  12.         curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
  13.         curl_setopt($h, CURLOPT_TIMEOUT, 10);
  14.         curl_setopt($h, CURLOPT_HTTPHEADER,
  15.             array('Authorization: Bearer ' . $idtoken));
  16.        
  17.         $response = curl_exec($h);
  18.      
  19.         if ( (int)curl_getinfo($h)['http_code'] === 200 ) {  
  20.             $jwt = json_decode($response, true);  
  21.             $Ok = ( $jwt['active'] == true );
  22.         }
  23.     }
  24.  
  25.     if ( $Ok AND isset($_SERVER["HTTP_REFERER"]) ) {
  26.         $urlParts = parse_url($_SERVER["HTTP_REFERER"]);
  27.         if ( $urlParts['host'] !== $_SERVER["HTTP_HOST"] ) {
  28.             // CORS : autoriser l'origine
  29.             $issuer = $urlParts['scheme'] . "://" . $urlParts['host'];
  30.             include_spip('inc/headers');    
  31.             header('Access-Control-Allow-Origin', $issuer);
  32.         }
  33.     }
  34.  
  35.     return $Ok;
  36.  
  37. }

Télécharger

Variante avec méthode GET pour SPIP :
SPIP

  1. /*
  2. Autorisation avec OAuth Server by DnC
  3. OpenID Connect : Introspection, méthode GET  
  4. */
  5.  
  6. function oauth_authorize($idtoken) {
  7.  
  8.     $Ok = false;
  9.  
  10.     if ( !empty( $idtoken) ) {
  11.  
  12.         // Interroger l'introspection de OAuth Server by DnC
  13.         include_spip('inc/distant');
  14.         $url = "http://oa.dnc.global/introspect?token=" . $idtoken;
  15.         $response = recuperer_url($url);  
  16.        
  17.         if ( (int)$response['status'] === 200 ) {
  18.             $jwt = json_decode($response['page'], true);
  19.             if ( $jwt['active'] == true ) {
  20.                 if ( isset($_SERVER["HTTP_ORIGIN"]) ) {
  21.                     // Accès HTTP (CORS) : autoriser l'origine
  22.                     include_spip('inc/headers');
  23.                     $issuer = trim(strtr($_SERVER["HTTP_ORIGIN"], '<>"\'', '[]##'));    
  24.                     header('Access-Control-Allow-Origin', $issuer);
  25.                 }
  26.                 $Ok = true;
  27.             }
  28.         }
  29.  
  30.     }
  31.  
  32.     return $Ok;
  33.  
  34. }

Télécharger

Un exemple plus complet, faisant apparaître la totalité des erreurs possibles, figure ici : OpenID Connect : Exemples complets du flux d’Autorisation via un code puis requête UserInfo.

Notes :

- Dans le cas où l’application cliente et le serveur de données protégées se trouvent dans des domaines différents, il faut gérer l’autorisation HTTP, comme cela est fait dans l’exemple ci-dessus. Voyez Contrôle d’accès HTTP (CORS).

- L’interrogation du serveur d’autorisation à chaque accès d’une ressource protégée peut le surcharger. Pour éviter cela, on peut mettre en cache la réponse du serveur du côté du serveur de ressource. Avec SPIP, c’est le rôle de la fonction recuperer_url_cache() qui pourra remplacer recuperer_url() dans l’exemple précédent. La fonction permet de régler le délai de garde en cache, qu’il convient de fixer à une durée assez courte (10 secondes par exemple), l’essentiel étant de ne pas bombarder le serveur. Voici un exemple :

SPIP

  1. $res = recuperer_url_cache( $url, array('delai_cache' => 10) ); // Délai de 10s

La fonction décrite précédemment peut être utilisée dans une fonction d’autorisation d’accès à un objet SPIP ( ici l’objet gis de radar ) :

SPIP

  1. function _autoriser_gis($faire, $quoi, $id, $qui, $options) {
  2.     if ( $qui['statut'] == '0minirezo' ) {
  3.         // Toujours autoriser un administrateur
  4.         return true;
  5.     } else {
  6.         if ( $idtoken = $_GET['token'] ) {
  7.             // Vérifier le jeton d'accès
  8.             return oauth_authorize($idtoken);    
  9.         } else return false;
  10.     }
  11. }

Télécharger

Il faut cependant noter que la mise en cache expose à la réutilisation du jeton par un malware.

Paramètre ’requester_ip’

Lorsque l’Introspection est demandée par une ressource protégée (distincte de l’application cliente à l’origine de l’authentification), il importe de ne pas répondre à un malware ayant intercepté le jeton et tentant de le ré-utiliser.

Pour cela, la ressource protégée doit transmettre l’IP du demandeur au moyen du paramètre ’requester_ip’.

La fonction d’introspection d’OAuthSD vérifie que l’IP indiquée se trouve dans le sous réseau de l’application cliente.

Exemple d’appel, avec le jeton passé par la méthode Auth Header et le paramètre ’requester_ip’ par Post :
PHP

  1.             // Method Bearer + parameters by Post
  2.             $data = array(
  3.                 'requester_ip' => $_SERVER['SERVER_ADDR'],
  4.             );
  5.             $authorization = "Authorization: Bearer ". $res1['id_token'];
  6.             $h = curl_init($introspection_endpoint);
  7.             curl_setopt($h, CURLOPT_HTTPHEADER, array(
  8.                 'Content-Type: application/x-www-form-urlencoded',
  9.                 'Authorization: Bearer ' . $id_token
  10.             ));
  11.             curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
  12.             curl_setopt($h, CURLOPT_POST, 1);  
  13.             curl_setopt($h, CURLOPT_POSTFIELDS, http_build_query($data));
  14.             ...

Télécharger

API OpenID Connect : Logout

  publié le par DnC

Le point d’extrémité logout permet la déconnexion "unique" (Single Logout, SLO) à partir d’une application (front channel single logout, RP generated logout etc.) : toutes les connexions d’un utilisateur sur les différentes applications sont invalidées.

Implémentation de la déconnexion

ll n’y a pas à ce jour (fin 2018) de "norme" définissant la déconnexion pour OpenID Connect. Pour s’en convaincre, il suffit de voir cet article très complet : OpenID Connect Logout et de considérer l’état d’avancement de la proposition de norme : Draft : Management de session OpenID Connect. Les nombreuses références à des propositions de standard (drafts) et la complexité des solutions proposées montrent la difficulté dans laquelle se trouve la communauté Openid pour adopter une solution. DnC propose pour OAuthSD une solution de déconnexion centralisée particulièrement simple s’appuyant sur le cookie SLI.

La déconnexion est générale (Single Logout, SLO) : toutes les connexions d’un utilisateur sur différentes applications sont invalidées.

Point d’extrémité de déconnexion unique (Single Logout Endpoint)

https://oa.dnc.global/logout

Forme de la demande de déconnexion unique

Une application effectue une demande de déconnexion comme une demande d’introspection en passant le jeton d’identité.

Le jeton est passé avec le paramètre "token" par l’une des méthodes suivantes : Auth Header, GET ou POST. Se reporter à API Open ID Connect : Introspection pour la description de l’appel.

Exemple d’appel :

  1. if ( $jwt['active'] == 'true' ) {
  2.  
  3.     // Post Methode  
  4.     $data1 = array(
  5.         'token' => $res['id_token'],
  6.     );
  7.  
  8.     // Effectuer un logout
  9.     $h = curl_init($logout_endpoint);
  10.     curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
  11.     curl_setopt($h, CURLOPT_TIMEOUT, 10);
  12.     curl_setopt($h, CURLOPT_POST, true);
  13.     curl_setopt($h, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));  
  14.     curl_setopt($h, CURLOPT_POSTFIELDS, http_build_query($data1));
  15.  
  16.     $res = curl_exec($h);
  17.     curl_close($h);
  18.  
  19.     if  ( empty($res['error'] ) ) {
  20.         // Continuer avec la déconnexion locale
  21.         ...
  22.     } else {
  23.         // ne pas déconnecter localement, signaler l'erreur.
  24.         ...
  25.     }
  26.  
  27. } else
  28.     // JWT is inactive
  29.     exit('Error : Invactive ID Token');
  30. }

Télécharger

Réponse du serveur

En cas de succès, le serveur retourne une réponse HTTP 200. Tous les jetons d’accès enregistrés sur le serveur pour l’utilisateur final connecté à l’application sont effacés. De ce fait, les cookies SLI éventuellement présents sur les agents de l’utilisateur seront détruits si une tentative de connexion est effectuée.

Notons que, contrairement au mécanisme de SLI qui est attaché à un agent utilisateur (un navigateur sur un poste de travail), la déconnexion unique s’applique à tous les agents que l’utilisateur aurait utilisés pour se connecter à des applications puisque tous les jetons d’accès enregistrés pour cet utilisateur seront effacés.

En cas d’échec de la requête, le serveur n’effectue pas la déconnexion et retourne une réponse HTTP 403.

Connaître l’état de connexion d’une application

Si on souhaite connaître l’état actuel de connexion d’une application, il suffit de répéter la demande d’authentification avec prompt = ’none’. Si la déconnexion a eu lieu, le serveur répondra avec l’erreur ’login_required’ et un code HTTP 403. Cette solution a l’avantage de répondre totalement à la norme OpenID Connect dans son état définitif.

Discussion, alternative

Au reçu du code HTTP 200, l’application cliente à partir de laquelle a été émis la déconnexion pourra procéder à la déconnexion locale (probablement effacer des données de session et détruire un cookie de connexion etc.).

La question se pose pour les autres applications clientes que l’utilisateur aurait connectées précédemment.

Ces applications seront déconnectées (ne pourront se reconnecter automatiquement par Silent Re-Authentication (SRA)) à la fin de la validité de leur session locale. Même si elles apparaissent connectées, les requêtes qu’elles tenteront d’effectuer en direction des ressources protégées par le serveur OIDC n’aboutiront pas. Le retour d’erreur en résultant permettra au concepteur de l’application de prévoir une déconnexion locale.

Si la durée de la session locale de l’application cliente est courte, on aboutira à une déconnexion dans un délai court. A la limite, il existe des application sans session qui interrogent le serveur OIDC à chaque sollicitation pour authentifier la requête qui leur est faite. Les applications mono page et les services HTTP REST (sant état) appartiennent à cette catégorie.

Quoiqu’il en soit, le concepteur d’une application devrait intégrer un mécanisme de suivi de la validité du jeton d’accès pour informer l’utilisateur local et déconnecter localement l’application si le jeton est périmé. C’est l’objet de notre Monitoring de l’état de l’authentification et SLO. Le dispositif de Logout rentre ainsi dans le cadre du management de session.

Une alternative à notre méthode de déconnexion est décrite ici : Draft : Management de session OpenID Connect. Bien qu’il soit inscrit dans ce projet de spécification que la connaissance de l’état de connexion actuel d’une application (pour faire apparaître la déconnexion) peut être fait "sans générer de trafic sur le réseau", il n’en est rien car on "interroge l’OP ... à un intervalle approprié". La technique introduit donc un délai d’apparition de la déconnexion.

Notre méthode de déconnexion apparaît donc très intéressante, notamment si on considère sa simplicité de mise en oeuvre au regard des importantes modifications à apporter aux applications requises par le management de session OpenID Connect dont l’avenir, de plus, nous parait mal assuré.

API OpenId Connect : Point d’extrémité d’informations sur les clefs (Keys Endpoint)

  publié le par DnC

Le protocole OpenID Connect utilise un jeton d’identité (ID Token) fondé sur JWT. La norme décrit comment une application cliente doit valider un ID Token reçu en réponse à une demande d’authentification. Un tiers (le serveur de ressource protégé notamment) peut évidemment appliquer la même méthode, à condition d’accéder aux informations nécessaires. La validation est effectuée à l’aide d’un cryptage asymétrique, mettant en œuvre une paire de clé publique-privée. Le serveur de ressource protégé (RS) doit donc pouvoir accéder à la clé publique.

C’est ce que permet le

Point d’extrémité d’informations sur les clefs (Keys Endpoint)

https://oa.dnc.global/keys

Ce point permet à un serveur de ressource d’accéder à la clé publique qui lui permettra de valider le jeton d’identité. Les données au format JSON sont définies dans le document JSON Web Key (JWK).

Voici un exemple de réponse au format JSON :
[JSON]

{"keys":[{
"kid":"618584200ef916a154008d898a1e7edc",
"kty":"RSA",
"alg":"RS256",
"use":"sig",
"e":"AQAB",
"n":"ykcWIXjQ-f61XCJutT4JcgpmmobtB0U7ZcejT8tBD8rOZPkQDYf0Q3pMjCkNT8RRKzMYtkelY2CNn3U7kVJMgbJAtvZsCdlChVHAKvRnjwh1GR_6Zpmajm5cuz4bjQWWUIPIoXe_4JbC8nCrHdaagzB_6PrV_NILyn5unG1RLOrWx7_yzLaterDKxHTCBeOlqv_5VGFey0Ecf-X7Bj8YRx6fpamK4BcEAZSAbZMtAnTckp3hOYJgZo3MOXDxSQw1YR83i5Udcoaf7sxfhEA_b7r9CeNfgj76MKM7sdCfBMI7_JSz-YU_pJKCuT9Ny3IJQ0fQHpDzSq2oD_3cDcLjfXTGM67rXElwr9l8yrSNa29UGK4q2u9cFCQmJGlxVhZU6bzs7l4202LTJdPlzm_29jwLVvtqnVJSovMLHx84ReFtus1RdKRGB2plDQccvBNvp92D9lOnM3bAu1fKRAJwNh3hg1d6k7MVCHxoo9HVnkxzW48rAAJE2nk44a2Y0cclufBhvKRdNavldS1XOyZ_qf3qCAzsuYF1VAga8I-QOb6OyXp0KGLptbyYD-ZXISGPw3pDD3aAof_PMfFhSB96GHDnm-UCRpFHndQ_fZgtZhWugU8z22rV-irYCySqVkpE0ToWbNXNFZ9Jo1GXdwkpi1WjB7S-ipjzRFOlxhwbvZ0"},
...

Notes :
- OAuthSD permet de définir une paire de clé publique/privée pour chaque application cliente. Le point d’extrémité d’informations sur les clés peut donc être appelé avec le paramètre ’aud’ donnant l’identifiant de l’application cliente (client_id). Si ce paramètre n’est pas fourni, les informations retournées sont celles des clés par défaut, souvent dénommées "clés du serveur".

API OpenID Connect : Découverte

  publié le par DnC

Le protocole OpenID Connect nécessite l’utilisation de plusieurs points d’extrémité pour authentifier les utilisateurs et pour demander des ressources, y compris des jetons, des informations sur l’utilisateur et des clés publiques.

Comment un serveur de ressource indépendant du serveur d’autorisation peut-il connaître les fonctionnalités du serveur et y accéder ?

Le document de découverte

Conformément à la spécification OpenID Connect Discovery 1.0, OAuthSD expose ses métadonnées à l’URI :

https://oa.dnc.global/.well-known/openid-configuration

Voici un exemple (non stable) des métadonnées fournies au format JSON :

[JSON]

{
 "issuer"                                : "https://oa.dnc.global",
 "token_endpoint"                        : "https://oa.dnc.global/token",
 "introspection_endpoint"                : "https://oa.dnc.global/introspect",
 "revocation_endpoint"                   : "https://oa.dnc.global/revoke",
 "authorization_endpoint"                : "https://oa.dnc.global/authorize",
 "userinfo_endpoint"                     : "https://oa.dnc.global/userinfo",
 "jwks_uri"                              : "https://oa.dnc.global/keys",
 "scopes_supported"                      : [ "openid",
                                             "profile",
                                             "email",
                                             "address",
                                             "phone"],
 "response_types_supported"              : [ "code",
                                             "id_token",
                                             "token id_token",
                                             "id_token token",
                                             "code id_token" ,
                                             "code token id_token" ],
 "response_modes_supported"              : [ "query",
                                             "fragment",
                                              "form_post" ],
 "grant_types_supported"                 : [ "authorization_code",
                                             "refresh_token",
                                             "password",
                                             "client_credentials",
                                             "urn:ietf:params:oauth:grant-type:jwt-bearer"],      
 "code_challenge_methods_supported"      : [ "S256",
                                             "plain" ],
 "subject_types_supported"               : [ "public" ],                                
 "token_endpoint_auth_methods_supported" : [ "client_secret_basic",
                                             "client_secret_post",
                                             "client_secret_jwt",
                                             "private_key_jwt" ],                                
 "token_endpoint_auth_signing_alg_values_supported" :
                                           [ "HS256",
                                             "HS512",
                                             "HS384",
                                             "RS256",
                                             "RS384",
                                             "RS512"],                                
 "id_token_signing_alg_values_supported" : [ "RS256",
                                             "RS384",
                                             "RS512",
                                             "HS256",
                                             "HS384",
                                             "HS512" ],
 "userinfo_signing_alg_values_supported" : [ "RS256",
                                             "RS384",
                                             "RS512",
                                             "HS256",
                                             "HS384",
                                             "HS512" ],  
 "claim_types_supported"                 : [ "normal" ],  
 "claims_supported"                      : [ "sub",
                                             "iss",
                                             "auth_time",
                                             "acr",
                                             "name",
                                             "given_name",
                                             "family_name",
                                             "nickname",
                                             "email",
                                             "email_verified" ],
 "ui_locales_supported"                  : [ "fr" ],  
 "claims_parameter_supported"            : true,  
 "request_parameter_supported"           : false,
 "request_uri_parameter_supported"       : false,
 "require_request_uri_registration"      : false

Voir également :
- API OpenId Connect : Point d’extrémité d’informations sur les clefs (Keys Endpoint)