Accueil > OpenID Connect OAuth Server par DnC > Développer > OpenID Connect > OpenID Connect : SSO, management de session etc.

SSO et connexion unique (Single Login Identification, SLI)

  publié le par DnC

Une fonctionnalité attendue de la délégation d’autorisation, que l’on associe naturellement à l’inscription unique (Single Sign On, SSO) est la capacité donnée à l’utilisateur final de ne se connecter qu’une seule fois pour accéder à différentes applications (Single Login Identification, SLI).

De façon standard, les flux d’OpenID Connect ne permettent pas de réaliser directement le SLI, mais des possibilités existent. On est là sur une technique peu divulguée, qui nécessite des développement significatifs du côté des applications.

A côté de ces méthodes standard, OAuthSD offre l’option de gérer le SLI et la ré-authentification silencieuse sans nécessiter de code spécifique du côté des applications clientes. Ceci est traité dans cet article : SLI, SLO et SRA sont dans un bateau : OAuthSD.

SSO et SLI

Le SSO n’implique pas automatiquement le SLI
Lorsque l’on évoque SSO (Single Sign On), on englobe souvent deux fonctionnalités :
- le fait de ne s’enregistrer (sign on) qu’une seule fois sur un serveur d’identité et de pouvoir se connecter (entrer son login et son mot de passe) sur différentes applications sans avoir à s’enregistrer sur chacune d’elle. C’est à proprement parler le SSO.
- le fait de se connecter à une application et de naviguer vers une autre sans avoir à entrer à nouveau son login et son mot de passe. Il s’agit de la Connexion Unique Single Connection, Single Login Identification (SLI).

Si le SSO est dans la nature même de OAuth 2 et OpenID Connect, il n’en est pas de même pour le SLI. En revanche, OpenID Connect offre "tout ce qu’il faut" pour le réaliser. Sans pour autant présenter de flux particulier pour cela : c’est donc une fonctionalité à développer du côté des applications (voir : Draft : Management de session OpenID Connect), ou bien une nouvelle fonctionnalité à ajouter [1] à OpenID Connect. C’est ce qu’offre OAuthSD, voir : SLI, SLO et SRA sont dans un bateau : OAuthSD .

Bien entendu, le SLI n’est possible que dans un ensemble d’applications liées à un même serveur d’identité, c’est à dire dans un espace de réseau et d’applications contrôlé par une même entité (corporate realm).

Applications situées dans le même domaine

Dans le cas d’applications situées dans le même domaine (par exemple dans diférents sous-domaine d’un domaine principal, en plus de ce domaine lui_même), une solution simple est de partager le cookie de session entre les applications. Pour cela, le concepteur des applications enregistrera le cookie pour le domaine principal au lieu de l’enregistrer pour le sous-domaine des applications (paramètres cookie_domain et cookie_path).

Cependant, les scopes sont particuliers aux applications. L’application cible doit donc ré-interroger le serveur d’autorisation pour obtenir un JWT qui lui soit propre.

Applications situées dans des domaines différents

Dans le cas d’applications situées dans des domaines différents le problème est un peu plus complexe.
La transmission d’autorisation est réputée sans solution sûre avec les flux OpenID Connect standard. En effet, l’application cible doit obtenir la certitude de l’origine du jeton reçu, et donc de l’identité de l’utilisateur final, sans pouvoir lui imposer une nouvelle demande d’identification. C’est à cela que sert la demande d’authentification silencieuse.

Demande d’authentification silencieuse (Silent Re-Authentication, SRA)

Le protocole OpenID Connect prend en charge un paramètre prompt = none sur la demande d’authentification qui permet aux applications d’indiquer que le serveur d’autorisations ne doit afficher aucune interaction de l’utilisateur (telle que l’authentification, le consentement). OAuthSD renverra la réponse demandée à l’application ou renverra une erreur si l’utilisateur n’est pas déjà authentifié, ou si un type de consentement ou une invite est requis avant de continuer.

Erreurs et suites

Les valeurs possibles pour ERROR_CODE sont définies par la spécification OpenID Connect :

- login_required : l’utilisateur n’était pas connecté à OAuthSD, l’authentification silencieuse n’est pas possible.
L’utilisateur doit être redirigé vers la page de connexion OAuthSD (sans le paramètre prompt =’none’) afin de s’authentifier.

- consent_required : l’utilisateur était connecté à OAuthSD, mais l’application attend son consentement (pour lequel il faudrait afficher une invite, ce qui est exclu par prompt=’none’).
D’après la norme, l’utilisateur devrait être redirigé vers la page de connexion OAuthSD comme précédemment.

Nous avons deux solutions :

  • éviter cette situation en considérant que les applications sont des applications de confiance, connues des utilisateurs, ou que les utilisateurs sont suffisamment avertis par ailleurs (par exemple en leur présentant les conditions générales du site à approuver lors de leur première connexion directe à l’application). Il suffira de modifier le flux pour ne pas retourner une erreur.
  • modifier le flux pour présenter la demande de consentement plutôt que retourner une erreur.

- interaction_required : l’utilisateur était connecté à OAuthSD et a autorisé l’application, mais doit être redirigé ailleurs avant que l’authentification ne puisse être complétée, par exemple, lors de l’utilisation d’une règle de redirection.
D’après la norme, l’utilisateur devrait être redirigé vers la page de connexion OAuthSD.
Cette situation ne devrait pas se produire dans un espace contrôlé par une même organisation (corporate realm), dans lequel le SLI devrait être mis en œuvre pour toutes les applications clientes. On se ramène donc aux deux cas précédents.

Ces erreurs sont retournées à l’application cliente qui a tenté la connexion. Ceci implique des développements significatif des applications clientes. Heureusement, il y a OAuthSD !

Remarque de sécurité :
Interroger Authorize avec prompt = ’none’ sans id_token_hint ne peut être considéré comme une authentification de l’utilisateur, mais comme une simple information sur sa connexion.
Voir : Ré-authentification silencieuse du sujet avec l’ID Token.

OAuthSD va plus loin ...

A côté de ces méthodes standard, OAuthSD offre l’option de gérer le SLI et la ré-authentification silencieuse sans nécessiter de code spécifique du côté des applications clientes : c’est le controleur authorize qui se charge de tout le travail.

SLI, SLO et SRA sont dans un bateau : OAuthSD

  publié le par DnC

Dans le cadre des flux OpenID Connect, outre la fonction d’inscription unique (Single Sign On, SSO) le serveur OAuthSD implémente la fonction d’identification unique (Single Login Identification, SLI), la déconnexion unique (Single Login Out, SLO) ainsi que la ré-authentification silencieuse (Silent Re-Authentication, SRA) [1]. Ceci sans modification du code d’interface des applications avec OIDC, tout étant pris en compte au niveau du contrôleur authorize.

Le code du serveur OAuthSD peut être paramétré pour implémenter (ou non) le SLI. La déconnexion unique (SLO) et la ré-authentification silencieuse (SRA) découlent du SLI.

Identification unique (Single Login Identification, SLI)

Lorsque le SLI est autorisé (ENABLE_SLI = true) ET que l’application cliente a été enregistrée sur le serveur avec le scope ’sli’, le contrôleur authorize traite une demande d’autorisation sans paramètre prompt comme ceci :

- La présence d’un cookie SLI enregistré sur l’agent dans le domaine du serveur (et non dans celui de l’application) est testée,
- si le cookie SLI est présent, non périmé et validé, il est utilisé pour l’authentification (le formulaire de login n’est pas présenté à l’utilisateur), le contrôleur retournant les jetons au client (le code d’autorisation dans le cas du flux d’autorisation avec code). A ce moment, la validité du cookie SLI est rafraichie à sa valeur initiale.
- En cas d’échec : soit il y a retour standard au client avec une erreur, soit le contrôleur présente le formulaire de login si la constante REAUTHENTICATE_NO_ROUNDTRIP est true. Dans un cas comme dans l’autre, le cookie SLI est d’abord détruit.

- Si le cookie SLI est absent :

  • si l’application cliente ne présente pas le scope ’sli’, le contrôleur agit de façon standard.
  • si l’application cliente autorise le SLI, et si l’authentification mentionnée précédemment réussit, un cookie SLI est enregistré sur l’agent dans le domaine du serveur. C’est le seul cas où le cookie SLI est créé.

Notes :
- Toute erreur "technique" (autre que les erreurs non-répétées de l’utilisateur dans l’entrée de son login ou de son mot de passe) conduit à la destruction du cookie SLI afin de protéger l’application contre les tentatives d’intrusion. Dans un tel cas le serveur ne répond pas pour ne pas orienter l’attaquant.
- Une tentative de connexion sous un identifiant d’utilisateur différent provoque également la destruction du cookie SLI, et conduit à la présentation du formulaire d’identification.
- Une tentative de se connecter depuis un poste de travail différent a le même effet.
- La fonctionnalité SLI est active dans les trois flux OpenID Connect, donc avec le plus sécurisé : Autorisation avec code. Ceci est un avantage par rapport à des solutions souvent présentées utilisant le flux Implicite.
- Le comportement des demandes avec prompt = ’none’, ’login’ et ’consent’ reste conforme au standard.
- Lorsque l’utilisateur change d’application et bénéficie du SLI, de nouvelles demandes de consentement pourraient lui être présentées.

Déconnexion unique (Single Login Out, SLO)

Le serveur expose un nouveau point de terminaison logout, permettant de déconnecter l’utilisateur de toutes les applications clientes.

La demande de déconnexion unique se fait comme une demande d’introspection en fournissant le jeton d’identité.

Lorsque la déconnexion est effectuée, tous les jetons d’accès enregistrés sur le serveur pour le sujet considéré 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.

Notez que les application clientes doivent gérer la déconnexion à leur niveau, il n’y a pas de miracle !

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 ’login_required’. Cette solution a l’avantage de répondre totalement à la norme OpenID Connect dans son état définitif.

Ré-authentification silencieuse (Silent Re-Authentication, SRA)

Dans le cadre du serveur OAuthSD, cette fonctionnalité résulte directement du SLI [2].

S’agissant des applications n’autorisant pas le SLI, elles ne bénéficient pas du SRA. Mais il est probable que les raisons qui ont conduit à ne pas autoriser le SLI conduiraient à ne pas autoriser le SRA.

Notons qu’il est toujours possible de Rafraîchir (actualiser) un jeton d’accès.

Le Cookie SLI

Le cookie SLI est un concept propre à DnC permettant au serveur OAuthSD d’offrir toutes les fonctionnalités de la connexion unique (SLI, SLO, SRA, SLO) sans modification des applications clientes pourvu qu’elles soient déjà interfacées avec OpenID Connect. Compte-tenu de la complexité et de la diversité des solutions proposées par ailleurs, qui n’ont pas fait à ce jour (début 2019) de spécification définitive, c’est une contribution majeure d’OAuthSD à OpenID Connect.

Le cookie SLI est créé par le contrôleur Authorize si le SLI est autorisé pour l’application cliente (scope ’sli’).

Il existe des informations complémentaires, connectez vous pour les voir.

Notes

[1Ces fonctionnalités sont souvent comprises comme faisant partie du SSO, mais en toute rigueur le SSO ne signifie que "inscription unique".

[2D’autres implémentations font appel à de nombreux développements du côté des applications (voir Draft : Management de session OpenID Connect). La solution offerte par OAuthSD a l’énorme avantage de ne pas nécessiter de modification à l’application cliente. Cependant, la solution n’est pas standard. Le management de session OpenID Connect faisant l’objet d’une proposition de standard, OAuthSD devra également offrir cette méthode dans le futur.

SRA et durée de la session de l’utilisateur

  publié le par DnC

La ’durée de la session locale’ est définie par le concepteur de l’application cliente. Dans le cadre d’OAuthSD, avec les flux entrant sur le contrôleur OpenID Authorize, comment définir en rapport les durées de vie des jetons et du cookie SLI ?

Durée de la session locale

La ’durée de la session locale’ est définie par l’application. Généralement, après authentification, une application définit une durée de validité de la connexion qui en résulte, ou "durée de la session". Lorsque celle-ci a dépassé sa fin de vie ("la session a expiré"), une nouvelle authentification est demandée à l’utilisateur s’il tente une nouvelle action nécessitant l’authentification.

L’utilisateur perçoit la durée de la session locale en ayant la mauvaise surprise de se voir présenter une nouvelle demande de connexion. L’objet de la ré-authentification silencieuse est de prolonger la session de façon transparente.

Ré-authentification silencieuse (Silent Re-Authentication, SRA)

Avec OAuthSD, si l’application a autorisé le SLI, et si la demande d’authentification est effectuée sans définir le paramètre prompt, il y a ré-authentification silencieuse (SRA) : plutôt que présenter un dialogue d’authentification, le contrôleur OpenID Authorize :
- vérifie l’existence d’un cookie SLI valide,
- vérifie que le jeton d’accès associé à l’authentification n’a pas expiré
pour répondre immédiatement avec un nouveau code d’accès [1].

L’application cliente poursuit normalement avec une demande de jetons.

Les effets de bord sont les suivants :
- le serveur rafraîchit le cookie SLI dont la durée de vie est étendue de SLI_COOKIE_LIFETIME [2] secondes,
- le jeton d’accès (Access Token) est renouvelé avec une durée de vie étendue de ACCESS_TOKEN_LIFETIME secondes,
- le jeton d’identité (ID Token) est renouvelé avec une durée de vie étendue de ID_TOKEN_LIFETIME secondes.

Rapport entre les durées de vie

Le contrôleur Authorize considère que l’utilisateur final est connecté si le jeton d’accès n’a pas expiré. On en déduit une première règle :
- Le SRA ne peut fonctionner que si ACCESS_TOKEN_LIFETIME > ’durée de la session locale’.

Si l’utilisateur n’effectue plus d’action nécessitant l’authentification (sur une des applications authentifiées avec SLI), le cookie SLI expirera SLI_COOKIE_LIFETIME après le dernier SRA, ce qui est un terme mal déterminé pour l’utilisateur [3]. Ceci incite à :
- fixer une durée de vie du cookie SLI assez importante, typiquement plusieurs fois la ’durée de la session locale’.

Pour ce qui est de la durée de vie du jeton d’identité (ID Token), qui joue le même rôle que le jeton d’accès, il parait adéquat de :
- fixer la durée de vie ID_TOKEN_LIFETIME égale à celle du jeton d’accès.

Valeurs par défaut de la configuration d’OAuthSD

Le valeurs suivantes sont adoptées par défaut dans le fichier configure.php :
ACCESS_TOKEN_LIFETIME : 7200s
ID_TOKEN_LIFETIME : 7200s
SLI_COOKIE_LIFETIME : 7200s x 3
Une ’durée de la session locale’ de 3600s serait cohérente avec ces valeurs.

Notons que le SRA sondera le contrôleur Authorize au rythme de ’durée de la session locale’ tant que l’utilisateur effectue des actions nécessitant l’authentification.

Considérations relatives à la sécurité

Si l’utilisateur n’effectue plus d’action nécessitant l’authentification sur une application donnée, celle-ci se déconnectera localement après ’durée de la session locale’. En effet, le SRA ne fonctionne que sur une demande d’authentification, elle-même déclenchée par une action de l’utilisateur après la fin de vie de la session locale. Pour autant, le serveur considérera l’utilisateur comme connecté tant que le jeton d’accès n’a pas expiré, et effectuera une reconnexion silencieuse à la première action nécessitant l’authentification. Dans ce cas, il est important de se rendre compte que la déconnexion locale n’est qu’apparente.

On pourrait donc considérer que le SRA ne va pas dans le sens de la sécurité. En effet, la limitation de durée des sessions a pour objet de déconnecter automatiquement les applications ouvertes sur un un poste de travail abandonné par son utilisateur.
Cependant, même les durées de session les plus courte sont rarement inférieures à 15mn. Ce temps est largement suffisant pour permettre une compromission.
Ce n’est donc pas tant le SRA qui est en cause, mais le fait d’abandonner sans surveillance une session ouverte.

La seule bonne parade est de déconnecter systématiquement toutes les applications avant de quitter son poste de travail, c’est le but de la Déconnexion unique (Single LogOut, SLO).

Que faire si la durée de session locale est mal connue ou non maîtrisée ?

Il arrive que la durée de la session locale soit variable ou extensible. C’est par exemple le cas lorsque le dialogue de connexion à l’application comporte une option du genre "Conserver ma connexion" ou "Se souvenir de moi" etc.
Dans ce cas, la ’durée de la session locale’ peut être beaucoup plus grande que la durée de vie fixée pour les jetons et le mécanisme SRA ne fonctionne pas, ou n’a pas vraiment d’objet.

Si malgré tout on veut absolument faire fonctionner le SRA, y compris sur de très longues ’durée de la session locale’, il est toujours possible de programmer l’application cliente pour qu’elle rafraîchisse systématiquement le jeton d’accès. Cela doit être fait avant qu’il n’arrive à péremption, sinon la déconnexion unique ne fonctionnera pas pour cette application.

Du point de vue de la sécurité, notons que le mécanisme SLI + SRA fera en sorte que toutes les applications pour lesquelles le SLI est autorisé, et auxquelles l’utilisateur sera connecté à partir du même poste de travail, hériteront de cette extension de la durée de vie apparente de leur session.

Notes

[1Dans le cas contraire, le dialogue d’identification est présenté, ce qui différencie le SRA de l’appel à Authenticate avec prompt = ’none’.

[2Voir le fichier /oidc/includes/configure.php.

[3Sauf à ce que l’application cliente vérifie fréquemment la connexion (monitoring) et signale l’approche de l’échéance.

Monitoring de l’état de l’authentification et SLO

  publié le par DnC

Le monitoring de l’état de l’authentification a pour but de synchroniser la connexion locale d’une application avec le jeton d’accès correspondant.

Introduction

OAuthSD, suivant en cela OAuth 2.0, considère que l’utilisateur final est connecté à une application tant que le jeton d’accès associé est valide.

Il n’y a pas "naturellement" de relation directe entre ce jeton et l’état de connexion local d’une application. Chaque application devra donc mettre en place un monitoring, côté client, dans le but de :
- surveiller la validité du jeton d’accès,
- compléter le mécanisme de déconnexion unique (Single Logout, SLO) en provoquant la déconnexion locale de l’application lorsque le jeton d’accès a expiré.

La communauté se voit proposer une "norme" de gestion de la session OpenID Connect (voir : Draft : Management de session OpenID Connect) qui s’appuie sur des iframes du côté du serveur et du côté de l’application. Il ne s’agit là que d’un procédé de communication entre une application et le serveur OIDC, communication qui peut être réalisée par d’autres moyens, et notamment par l’utilisation de Javascript côté client.

Nous pensons (peut-être à tort ?) qu’une "norme" concernant une couche ISO de niveau protocole ne doit pas imposer un procédé sur une couche de niveau inférieur. De plus, une implémentation côté client s’appuyant sur la norme OpenID Connect telle qu’elle existe actuellement est bien préférable à une nouvelle complication du serveur OIDC.

A l’appui de cette affirmation, nous présentons la méthode de monitoring mise en oeuvre pour SPIP :

Implémentation du monitoring dans le plugin OpenID Connect client pour SPIP

Le site oa.dnc.global utilise ce plugin (il est client OIDC, le serpent se mord la queue !). Vous pouvez donc voir une étiquette en haut à gauche de la fenêtre qui traduit l’état du jeton d’accès lié à la connexion courante :

icône Signification
La connexion de l’utilisateur final n’est pas établie avec le serveur OIDC. Notez que l’utilisateur peut s’être connecté à l’application à l’aide de la méthode de connexion classique de SPIP.
L’utilisateur final et l’application sont connectés par OIDC.
Erreur générale, traduit malheureusement de multiples situations :
- perte de communication Internet,
- défaut de configuration du plugin,
- défaut d’inscription de l’application cliente sur le serveur OAuthSD,
- inscription de l’application sur un autre serveur, non compatible OAuthSD,
- login inconnu du serveur OAuthSD (l’utilisateur final n’est pas inscrit sur ce serveur),
- etc.
Le message peut éventuellement être plus explicite : "authentication error", ...

Le Monitoring permet également de manager la session OIDC et notamment d’effectuer la déconnexion globale (SLO).
En cliquant sur l’icône, des popups apparaissent selon l’état de connexion.

Exemple du code Javascript (avec jQuery) utilisé pour SPIP. Le plugin insère ce code en pied de chaque page.

[SPIP]

  1. /**
  2. * Plugin OpenID Connect client pour SPIP
  3. * OIDC Client Monitoring
  4. * Interroge le contrôleur OIDC Authorize avec prompt=none pour afficher l'état de connexion.
  5. * Voir également action_logout() dans oidcclient_options.php.
  6. * Auteur : B.Degoy DnC
  7. * Copyright (c) 2019 B.Degoy
  8. * Licence GPL v3.0
  9. */
  10.  
  11.     // OIDC Client Monitoring
  12.  
  13.     <?php
  14.     include_spip('inc/oidcclient_configuration'); ?>
  15.  
  16.     $(document).ready(function() {
  17.  
  18.         // Ajouter l'étiquette si elle n'existe pas
  19.         if($('#oidc').length === 0){
  20.             $('<div id="oidc">&nbsp;OIDC&nbsp;</div>   ')
  21.             .appendTo('nav')
  22.             .css('position','absolute')
  23.             .css('top','12px')
  24.             .css('left','3px')
  25.             .css('color','white')
  26.             .css('padding','3px');    
  27.         }
  28.  
  29.         var login = "<?php echo @$GLOBALS['visiteur_session']['login']; ?>";
  30.         var state = "<?php echo @$GLOBALS['visiteur_session']['state']; ?>";
  31.  
  32.         if ( login !== "" ) {
  33.             // Si on est logé localement, surveiller qu'on l'est également sur OIDC
  34.             setInterval(function(){
  35.                 $.ajax({
  36.                     type : "get",
  37.                     url : "<?php echo OIDC_AUTHORIZATION_ENDPOINT; ?>",
  38.                     data : { 'response_type' : 'code',
  39.                         'client_id' : "<?php echo OIDC_CLIENT_ID; ?>",
  40.                         'user_id' : login,
  41.                         'state' : state,
  42.                         'scope' : 'openid',
  43.                         'prompt' : 'none',
  44.                     },
  45.                     statusCode : {
  46.                         401 : function(){
  47.                             // Non authentifié sur OIDC, déconnecter localement
  48.                             var url_logout_public = "<?php echo '?action=logout&logout=local&url=' . $GLOBALS['REQUEST_URI']; ?>";
  49.                             window.location.replace(url_logout_public);
  50.                         },
  51.                         200 : function (){
  52.                             // Signaler la connexion
  53.                             $('#oidc').css('background-color', '#8f8');
  54.                             $('#oidc').text(' OIDC ');
  55.                         },
  56.  
  57.                     },
  58.                     error : function(obj,textStatus,errorThrown){
  59.                         // Signaler qu'on ne sait pas
  60.                         $('#oidc').css('background-color', 'red');
  61.                         $('#oidc').text(textStatus + ' ' + errorThrown);
  62.                     }
  63.                 });    
  64.             }, 2000);
  65.  
  66.         } else {
  67.             // Signaler la déconnexion
  68.             $('#oidc').css('background-color', 'orange');
  69.             $('#oidc').text(' OIDC ');
  70.         }
  71.  
  72.     });

Télécharger

Implémentation du monitoring dans une extension Wordpress

 Le plugin est disponible sur Github.

Le code Javascript est inséré dans un hook appelé par la fonction bootstrap() de l’extension :
[PHP]

  1. /**
  2.     * OAuthSD project https://oa.dnc.global
  3.     * OAuthSD OIDC plugin for WordPress
  4.     * Insert monitoring code in footer
  5.     * dnc1
  6.     * Author : bdegoy DnC https://degoy.com
  7.     * copyright (c) 2019 B.Degoy DnC
  8.     * Licence GPL v3.0
  9.     */
  10.  
  11.     function insert_monitoring() {
  12.      
  13.         // Enqueue some jQuery UIs
  14.         wp_enqueue_script('jquery-ui-dialog'); // from WP core
  15.         // get registered script object for jquery-ui
  16.         global $wp_scripts;
  17.         $ui = $wp_scripts->query('jquery-ui-core');
  18.         // load the Smoothness theme from Google CDN  
  19.         $protocol = is_ssl() ? 'https' : 'http';
  20.         $url = "$protocol://ajax.googleapis.com/ajax/libs/jqueryui/{$ui->ver}/themes/smoothness/jquery-ui.min.css";
  21.         wp_enqueue_style('jquery-ui-smoothness', $url, false, null);
  22.            
  23.         // OIDC Client Monitoring
  24.         $thisuri = $_SERVER['REQUEST_URI'];
  25.         $absolutepath_this_plugin = plugin_dir_path( __FILE__ );
  26.         $url_this_plugin = plugins_url('', dirname(__FILE__) ) . "/openid-connect-generic";
  27.         $state = md5(wp_get_session_token());
  28.        
  29.         // Server URLs
  30.         $settings = $this->settings;
  31.         $url_endpoint_login = $settings->endpoint_login;
  32.         $url_endpoint_token = $settings->endpoint_token;
  33.         $url_endpoint_userinfo = $settings->endpoint_userinfo;
  34.         $url_endpoint_logout = $settings->endpoint_end_session;
  35.         $parts = parse_url($url_endpoint_login);
  36.         $url_server = $parts['scheme'] . '://' . $parts['host'];
  37.        
  38.         // OIDC user
  39.         $clientID = $settings->client_id;
  40.         // $userID = ???
  41.        
  42.         // WP user
  43.         $user = get_user_by('id', get_current_user_id());
  44.         $login = $user->user_login;
  45.         $nom_auteur =$user->display_name;
  46.        
  47.         // for info popup
  48.         $infos =
  49.         '<br/>' . __('oidcclient:serveur_url','openid-connect-generic') . ' : <a href="' . $url_server . '">' . $url_server . '</a>' .
  50.         '<br/>' . __('oidcclient:client_id','openid-connect-generic') . ' : ' . $clientID .
  51.         '<br/>' . __('oidcclient:login_wp','openid-connect-generic') . ' : ' . $login .
  52.         //'<br/>' . __('oidcclient:login_oidc_court','openid-connect-generic') . ' : ' .  $userID .
  53.         '<br/>' . __('oidcclient:nom_auteur','openid-connect-generic') . ' : ' .  $nom_auteur;
  54.        
  55.         // labels and messages
  56.         $msg_session_connected_no = __('oidcclient:session_connected_no','openid-connect-generic');
  57.         $msg_session_connected_yes = __('oidcclient:session_connected_yes','openid-connect-generic');
  58.         $msg_session_connected_error = __('oidcclient:session_connected_error','openid-connect-generic');
  59.         $msg_session_open = __('oidcclient:session_open','openid-connect-generic');
  60.         $msg_session_extend = __('oidcclient:session_extend','openid-connect-generic');
  61.         $msg_session_close = __('oidcclient:session_close','openid-connect-generic');
  62.         $msg_session_expires = __('oidcclient:session_expires','openid-connect-generic');
  63.         $lbl_yes = __('oidcclient:item_yes','openid-connect-generic');
  64.         $lbl_no = __('oidcclient:item_no','openid-connect-generic');
  65.         $lbl_t_session_restant = __('oidcclient:t_session_restant','openid-connect-generic');
  66.         $lbl_delai_reponse = __('oidcclient:delai_reponse','openid-connect-generic');
  67.         $lbl_infos_titre = __('oidcclient:lbl_infos_titre','openid-connect-generic');
  68.        
  69.         // link to OIDC login                                                                                        
  70.         $link_login = $this->client_wrapper->get_authentication_url();
  71.        
  72.         // link to logout page
  73.         $url_logout = esc_url($url_this_plugin . "/oidc_logout.php?url=" . $_SERVER['REQUEST_URI']);
  74.    
  75.         echo <<<JSCODE
  76.            
  77. <script type="text/javascript">
  78.    
  79. (function($) {
  80.  
  81.     var login = "$login";
  82.     var timeleft = 0;
  83.     var connected = 0;
  84.     var connectionMsg = '';
  85.     var interval = null;
  86.     var pollperiod = 60000;
  87.     var tagappendto = '#content';
  88.     var tagtop = '120px';
  89.     var tagleft = '16px';
  90.     var responseDelay = 'Unk';
  91.  
  92.     $(document).on('ready',function(){
  93.  
  94.         // Add OIDC labels
  95.         if($('#oidc').length === 0){
  96.             $('<div id="oidc"><span id="oidctag">&nbsp;OIDC&nbsp;</span><span id="oidcinfo">&nbsp;?&nbsp;</span></div>')
  97.             .appendTo(tagappendto);
  98.             //
  99.             $('#oidc')
  100.             .css('position','absolute')
  101.             .css('top',tagtop)
  102.             .css('left',tagleft);
  103.             //
  104.             $('#oidctag')
  105.             .css('color','white')
  106.             .css('padding','3px')
  107.             .css('z-index','10000')
  108.             .on('click', function(){
  109.                 switch (connected) {
  110.                     case 0 :
  111.                         connectionMsg = "$msg_session_connected_no";
  112.                         SessionOpenDialog(connectionMsg);
  113.                         break;
  114.                     case 1 :
  115.                         connectionMsg = "$msg_session_connected_yes";
  116.                         SessionCloseDialog(connectionMsg);
  117.                         break;
  118.                     default :
  119.                     case -1 :
  120.                         connectionMsg = "$msg_session_connected_error";
  121.                         break;
  122.                 };
  123.             });
  124.             //
  125.             $('#oidcinfo')
  126.             .css('color','white')
  127.             .css('padding','3px')
  128.             .css('z-index','10001')
  129.             .css('background-color','#09f')
  130.             .on('click', function(){
  131.                 $('<div></div>').appendTo('body')
  132.                 .html('<div><h6>$infos<br/>$lbl_t_session_restant : ' + timeleft + ' s<br/>$lbl_delai_reponse : ' + responseDelay + ' ms</h6></div>')
  133.                 .dialog({
  134.                     modal: true, title: "$lbl_infos_titre", zIndex: 10000, autoOpen: true,
  135.                     width: 'auto', resizable: false,
  136.                     close: function (event, ui) {
  137.                         $(this).remove();
  138.                         interval = setInterval(pollOidc,pollperiod);
  139.                         }
  140.                     });
  141.                 }
  142.             );            
  143.         }
  144.  
  145.         // If user is logged locally, verify the OIDC session is valid.  
  146.         if ( login !== "" ) {
  147.             pollOidc();
  148.             interval = setInterval(pollOidc,pollperiod);
  149.  
  150.         } else {
  151.             connected = 0;
  152.             // Show not OIDC connected.
  153.             $('#oidctag').css('background-color', 'orange');
  154.             $('#oidctag').text(' OIDC ');
  155.         }
  156.  
  157.         function SessionCloseDialog(message) {    //[dnc28d]
  158.             clearInterval(interval);
  159.             $('<div></div>').appendTo('body')
  160.             .html('<div><h6>'+message+'?</h6></div>')
  161.             .dialog({
  162.                 modal: true, title: "$msg_session_close", zIndex: 10000, autoOpen: true,
  163.                 width: 'auto', resizable: false,
  164.                 buttons: [
  165.                     {
  166.                         text: "$lbl_yes",
  167.                         click: function () {
  168.                             // Close the OIDC session.
  169.                             window.location.replace("$url_logout");
  170.                             $(this).dialog("close");
  171.                         }
  172.                     },{
  173.                         text: "$lbl_no",
  174.                         click: function () {                                                              
  175.                             $(this).dialog("close");
  176.                             interval = setInterval(pollOidc,pollperiod);
  177.                         }
  178.                     }
  179.                 ],
  180.                 close: function (event, ui) {
  181.                     $(this).remove();
  182.                     interval = setInterval(pollOidc,pollperiod);
  183.                 }
  184.             });
  185.         };
  186.  
  187.         function SessionOpenDialog(message) {    //[dnc28d]
  188.             clearInterval(interval);
  189.             $('<div></div>').appendTo('body')
  190.             .html('<div><h6>'+message+'?</h6></div>')
  191.             .dialog({
  192.                 modal: true, title: "$msg_session_open", zIndex: 10000, autoOpen: true,
  193.                 width: 'auto', resizable: false,
  194.                 buttons: [
  195.                     {
  196.                         text: "$lbl_yes",
  197.                         click: function () {
  198.                             // Se connecter
  199.                             window.location.replace("$link_login");
  200.                             $(this).dialog("close");
  201.                         }
  202.                     },{
  203.                         text: "$lbl_no",
  204.                         click: function () {                                                                
  205.                             $(this).dialog("close");
  206.                             interval = setInterval(pollOidc,pollperiod);
  207.                         }
  208.                     }
  209.                 ],
  210.                 close: function (event, ui) {
  211.                     $(this).remove();
  212.                     interval = setInterval(pollOidc,pollperiod);
  213.                 }
  214.             });
  215.         };
  216.  
  217.         function ExtendDialog(message) {    //[dnc28d]
  218.             if ( !$("#extenddialog").size() ) {  //[dnc28f]
  219.                 clearInterval(interval);
  220.                 $('<div id="extendeddialog"></div>').appendTo('body')
  221.                 .html('<div><h6>'+message+'?</h6></div>')
  222.                 .dialog({
  223.                     modal: true, title: "$msg_session_extend", zIndex: 10000, autoOpen: true,
  224.                     width: 'auto', resizable: false,
  225.                     buttons: [
  226.                         {
  227.                             text: "$lbl_yes",
  228.                             click: function () {
  229.                                 // Extend session
  230.                                 $.ajax({
  231.                                     type : "get",
  232.                                     url : "$url_endpoint_login",
  233.                                     data : { 'response_type' : 'code',
  234.                                         'client_id' : "$clientID",
  235.                                         'user_id' : login,
  236.                                         'state' :  "$state",
  237.                                         'scope' : 'openid sli',
  238.                                     }
  239.                                 });
  240.                                 $(this).dialog("close");
  241.                                 interval = setInterval(pollOidc,pollperiod);
  242.                             }
  243.                         },{
  244.                             text: "$lbl_no",
  245.                             click: function () {                                                                
  246.                                 $(this).dialog("close");
  247.                                 interval = setInterval(pollOidc,pollperiod);
  248.                             },
  249.                         }
  250.                     ],
  251.                     close: function (event, ui) {
  252.                         $(this).remove();
  253.                         interval = setInterval(pollOidc,pollperiod);
  254.                     },
  255.                 });
  256.             }
  257.         };
  258.  
  259.         // Test OIDC connection.
  260.         function pollOidc(){
  261.             connected = -1;
  262.             var d = new Date();
  263.             var timeStart = d.getTime();
  264.             var timeStop = 0;
  265.             $.ajax({
  266.                 type : "get",
  267.                 url : "$url_endpoint_login",
  268.                 data : { 'response_type' : 'code',
  269.                     'client_id' : "$clientID",
  270.                     'user_id' : login,
  271.                     'state' :  "$state",
  272.                     'scope' : 'openid',
  273.                     'prompt' : 'none',
  274.                 },
  275.                 statusCode : {
  276.                     401 : function(){
  277.                         connected = 0;
  278.                         var d = new Date();
  279.                         timeStop = d.getTime();
  280.                         // Not (or no longer) connected on OIDC, disconnect locally
  281.                         window.location.replace("$url_logout" +"&logout=local");
  282.                     },
  283.                     200 : function ( data, textStatus, jqXHR){
  284.                         connected = 1;
  285.                         var d = new Date();
  286.                         timeStop = d.getTime();
  287.                         // Show OIDC connected
  288.                         $('#oidctag').css('background-color', '#8f8');
  289.                         $('#oidctag').text(' OIDC ');
  290.                         timeleft = data['timeleft'];
  291.                         if ( timeleft < 600 ) {  //[dnc28d]
  292.                             // Approaching OIDC session end.
  293.                             clearInterval(interval);
  294.                             ExtendDialog("$msg_session_expires");
  295.                             interval = setInterval(pollOidc,pollperiod);
  296.                         }
  297.                     },
  298.                 },
  299.                 error : function(obj,textStatus,errorThrown){
  300.                     connected = -1;
  301.                     // Show error (OIDC state is unknown)
  302.                     $('#oidctag').css('background-color', 'red');
  303.                     $('#oidctag').text(textStatus + ' ' + errorThrown);
  304.                 },
  305.                 complete : function ( data, textStatus, jqXHR){
  306.                     if ( timeStop && timeStart ) {
  307.                         responseDelay = timeStop - timeStart;      
  308.                     } else {
  309.                         responseDelay = 'Unk';
  310.                     }
  311.                 },
  312.             });    
  313.         }
  314.  
  315.     });
  316. })( jQuery );
  317.  
  318. </script>
  319.  
  320. JSCODE
  321. ;
  322.    
  323. } //function

Télécharger

Notons qu’il faut insérer un bloc div en bas de page, ce qui est fait au moyen d’une action dans la fonction bootstrap en même temps que le hook de la fonction précédente :
[PHP]

  1. //OIDC Monitoring
  2.         wp_enqueue_script("jquery");
  3.         add_action('wp_footer', array( $plugin, 'insert_monitoring'), 5);

Télécharger

Avertir de la fin de session OIDC et la prolonger

  publié le par DnC

Le monitoring de l’état de l’authentification permet à l’utilisateur final de visualiser l’état de la session OIDC. Voici comment avertir l’utilisateur de l’imminence de la fin de session et lui permettre de la prolonger.

Le monitoring sera complet si l’utilisateur est averti de la fin imminente de la session OIDC et si il lui est proposé de la prolonger.

Lorsque l’on interroge Authorize avec prompt = ’none’, une particularité d’OAuthSD est de retourner dans le corps de la réponse le temps restant avant la fin de session (variable ’timeleft’, en secondes).

Toujours dans l’exemple du plugin OIDC Client pour SPIP, l’avertissement est implémenté en modifiant le code de l’objet ’200’ pour proposer à l’utilisateur de prolonger la session :

jQuery

  1. 200 : function ( data, textStatus, jqXHR){
  2.     // Signaler la connexion
  3.     $('#oidc').css('background-color', '#8f8');
  4.     $('#oidc').text(' OIDC ');
  5.     var timeleft = data['timeleft'];
  6.     if ( timeleft < 600 ) {  //[dnc28d]
  7.         // La fin de session approche
  8.         var retVal = confirm( "<?php echo _T('oidcclient:session_expire'); ?>" );
  9.         if( retVal == true ) {
  10.             // étendre la session
  11.             $.ajax({
  12.                 type : "get",
  13.                 url : "<?php echo OIDC_AUTHORIZATION_ENDPOINT; ?>",
  14.                 data : { 'response_type' : 'code',
  15.                     'client_id' : "<?php echo OIDC_CLIENT_ID; ?>",
  16.                     'user_id' : login,
  17.                     'state' :  state,
  18.                     'scope' : 'openid sli',
  19.                 }
  20.             });
  21.         }
  22.     }
  23. },

Télécharger

Les jetons sont rafraîchis en utilisant notre mécanisme de SLI, et non un jeton de rafraîchissement. Voyez ici pourquoi : Rafraîchir (actualiser) un jeton d’accès : § "Bonnes pratiques pour la sécurité".

Draft : Management de session OpenID Connect

  publié le par DnC

OpenID Connect Session Management.
Cette proposition [1] de spécification répond au besoin de connaître l’état réel de connexion de l’utilisateur et de gérer la déconnexion unique.
DnC suit l’évolution de cette spécification et OAuthSD devra intégrer les spécifications qui seront finalement approuvées.
Sans attendre, le serveur OAuthSD répond à la plus grande part des fonctionnalités évoquées avec la technique de la connexion unique et de la déconnexion unique qui en découle ainsi que le monitoring.

Note : Cette fonctionnalité n’est pas encore implémentée dans OAuthSD telle que décrite dans ce qui suit.

Traduction du document OpenID Connect Session Management 1.0 - draft 28

...

Cette spécification définit le terme suivant :

Session

Période continue pendant laquelle un utilisateur final accède à un RP (Relying Party) en s’appuyant sur l’authentification de l’utilisateur final effectuée par le fournisseur OpenID.

2. Découverte du point de terminaison

Pour prendre en charge la gestion de session OpenID Connect, le RP doit obtenir les URL de point de terminaison associées à la gestion de session. Ces URL sont normalement obtenues via la réponse à la découverte de l’OP, comme décrit dans OpenID Connect Discovery 1.0 [OpenID.Discovery], ou PEUVENT être apprises via d’autres mécanismes.

2.1. Métadonnées de découverte du fournisseur OpenID

Ces paramètres de métadonnées de fournisseur OpenID DOIVENT être inclus dans les réponses de découverte du serveur lorsque la gestion de session et la découverte sont prises en charge :

check_session_iframe

CHAMPS OBLIGATOIRES. URL d’un iframe de l’OP qui prend en charge les communications entre origines croisées pour les informations d’état de session avec le client RP, à l’aide de l’API postMessage de HTML5. La page est chargée à partir d’un iframe invisible incorporé dans une page du RP afin de pouvoir s’exécuter dans le contexte de sécurité de l’OP. Il accepte les requêtes postMessage en provenance de l’iframe du RP concerné et utilise postMessage pour renvoyer le statut de connexion de l’utilisateur final vers l’OP.

end_session_endpoint

CHAMPS OBLIGATOIRES. URL de l’OP sur laquelle un RP peut effectuer une redirection pour demander à l’utilisateur final d’être déconnecté de l’OP.

3. Création et mise à jour de sessions

Dans OpenID Connect, la session du RP commence généralement lorsque le RP valide le jeton d’identification de l’utilisateur final. Reportez-vous à la spécification OpenID Connect Core 1.0 [OpenID.Core] pour savoir comment obtenir un jeton ID et le valider. Lorsque l’OP prend en charge la gestion de session, il DOIT également renvoyer l’état de session en tant que paramètre supplémentaire session_state dans la réponse d’authentification. La réponse d’authentification OpenID Connect est spécifiée dans la Section 3.1.2.5 d’OpenID Connect Core 1.0.

Ce paramètre est :

session_state
Etat de session. Chaîne JSON [RFC7159] représentant l’état de connexion de l’utilisateur final sur l’OP. Il NE DOIT PAS contenir le caractère espace (""). Cette valeur est opaque pour le RP. Ceci est OBLIGATOIRE si la gestion de session est prise en charge.

La valeur de l’état de session est initialement calculée sur le serveur. La même valeur d’état de session est également recalculée par l’iframe dans le navigateur du client. La génération de valeurs appropriées pour l’état de session est spécifiée dans la section 4.2 et est basée sur un hachage cryptographique salé de l’ID client, de l’URL d’origine et de l’état du navigateur OP. Pour l’URL d’origine, le serveur peut utiliser l’URL d’origine de la réponse d’authentification, conformément à l’algorithme spécifié à la section 4 de la RFC 6454 [RFC6454].

4. Notification de changement d’état de session

Un jeton d’identité vient généralement avec une date d’expiration. Le RP PEUT s’y fier pour expirer la session du RP. Cependant, il est tout à fait possible que l’utilisateur final se soit déconnecté de l’OP avant la date d’expiration. Par conséquent, il est hautement souhaitable de pouvoir connaître le statut de connexion de l’utilisateur final à l’OP.

Pour ce faire, il est possible de répéter la demande d’authentification avec prompt = aucune [2]. Cependant, cela entraîne un trafic réseau, ce qui est problématique sur les appareils mobiles qui deviennent de plus en plus populaires. Par conséquent, une fois que la session est établie avec la demande d’authentification et la réponse, il est souhaitable de pouvoir vérifier l’état de la connexion sur l’opérateur sans générer de trafic sur le réseau en interrogeant un OP iframe caché à partir d’un RP iframe avec un postMessage à origine restreinte, comme suit.

4.1. RP iframe

Le RP charge un iframe invisible à partir de lui-même. Cette iframe DOIT connaître l’ID de l’OP iframe, comme décrit à la section 4.2, afin de pouvoir envoyer un message à l’OP iframe. L’iframe RP interroge l’OP iframe avec postMessage à un intervalle approprié [3] pour l’application RP. Avec chaque postMessage, il envoie l’état de session défini dans la section 4.2.

Le postMessage de l’iframe RP fournit la concaténation suivante en tant que données :

ID client + "" + état de session

Il doit également pouvoir recevoir le postMessage de l’OP iframe. Les données reçues seront soit modifiées, soit inchangées, sauf si la syntaxe du message envoyé est déterminée par le terminal opérateur comme étant malformée, auquel cas les données reçues seront erronées. Dès réception de la modification, le RP DOIT effectuer une nouvelle authentification avec invite = aucune pour obtenir l’état de la session en cours sur le terminal opérateur. À la réception d’une erreur, le RP NE DOIT PAS effectuer de nouvelle authentification avec invite = aucune, afin de ne pas causer de boucles infinies potentielles générant un trafic réseau vers le terminal opérateur.

Voici un exemple de pseudo-code non normatif pour l’iframe RP :

Javascript

  1. var stat = "unchanged";
  2.   var mes = client_id + " " + session_state;
  3.  
  4.   function check_session()
  5.   {
  6.     var targetOrigin = "https://server.example.com";
  7.     var win = window.parent.document.getElementById("op").
  8.                 contentWindow;
  9.     win.postMessage( mes, targetOrigin);
  10.   }
  11.  
  12.   function setTimer()
  13.   {
  14.     check_session();
  15.     timerID = setInterval("check_session()",3*1000);
  16.   }
  17.  
  18.   window.addEventListener("message", receiveMessage, false);
  19.  
  20.   function receiveMessage(e)
  21.   {
  22.     var targetOrigin = "https://server.example.com";
  23.     if (e.origin !== targetOrigin ) {return;}
  24.     stat = e.data;
  25.  
  26.     if stat == "changed" then take the actions below...
  27.   }

Télécharger

4. Notification de changement d’état de session

Un jeton d’identité vient généralement avec une date d’expiration. Le RP PEUT s’y fier pour expirer la session du RP. Cependant, il est tout à fait possible que l’utilisateur final se soit déconnecté de l’OP avant la date d’expiration. Par conséquent, il est hautement souhaitable de pouvoir connaître le statut de connexion de l’utilisateur final à l’OP.

Pour ce faire, il est possible de répéter la demande d’authentification avec prompt = none. Cependant, cela entraîne un trafic réseau, ce qui est problématique sur les appareils mobiles qui deviennent de plus en plus populaires. Par conséquent, une fois que la session est établie avec la demande d’authentification et la réponse, il est souhaitable de pouvoir vérifier l’état de la connexion sur l’opérateur sans générer de trafic sur le réseau en interrogeant un OP iframe caché à partir d’un RP iframe avec un postMessage à origine restreinte, comme suit.

4.2. OP iframe

Le RP charge également un OP iframe invisible à partir de check_session_iframe de l’OP. Le RP DOIT assigner un attribut id à l’iframe afin qu’il puisse l’adresser, comme décrit ci-dessus. L’OP iframe DOIT imposer que l’appelant ait la même origine que son cadre parent. Il DOIT rejeter les demandes postMessage de toute autre source.

Comme spécifié dans la section 4.1, le postMessage de l’iframe RP fournit la concaténation suivante en tant que données :

ID client + "" + état de session

L’op iframe a accès à l’état du navigateur dans l’OP (dans un cookie ou dans un stockage HTML5) qu’il utilise pour calculer et comparer l’état de session OP passé par le RP. L’op iframe DOIT le recalculer à partir de l’ID client précédemment obtenu, de l’URL d’origine de la source (à partir de postMessage) et de l’état actuel du navigateur OP. L’état de session inclut toutes ces informations pour des raisons de confidentialité, de sorte que différents clients actifs dans le même navigateur ont des valeurs d’état de session distinctes.

Si le postMessage reçu est syntaxiquement incorrect, de sorte que l’ID client publié et l’URL d’origine ne peuvent pas être déterminés ou sont incorrects sur le plan syntaxique, l’OP iframe DOIT poster (postMessage) à la source la chaîne d’erreur. Si la valeur reçue et la valeur calculée ne correspondent pas, l’OP iframe DOIT poster à la source la chaîne modifiée. S’il y a correspondance, alors il DOIT poster la chaîne inchangée.

Voici un exemple non normatif de pseudo-code pour l’OP iframe :

Javascript

  1. window.addEventListener("message", receiveMessage, false);
  2.  
  3.   function receiveMessage(e){ // e.data has client_id and session_state
  4.  
  5.     // Validate message origin
  6.     var client_id = e.data.split(' ')[0];
  7.     var session_state = e.data.split(' ')[1];
  8.     var salt = session_state.split('.')[1];
  9.  
  10.     // if message syntactically invalid
  11.     //     postMessage('error', e.origin) and return
  12.  
  13.     // get_op_browser_state() is an OP defined function
  14.     // that returns the browser's login status at the OP.
  15.     // How it is done is entirely up to the OP.
  16.     var opbs = get_op_browser_state();
  17.  
  18.     // Here, the session_state is calculated in this particular way,
  19.     // but it is entirely up to the OP how to do it under the
  20.     // requirements defined in this specification.
  21.     var ss = CryptoJS.SHA256(client_id + ' ' + e.origin + ' ' +
  22.       opbs + ' ' + salt) + "." + salt;
  23.  
  24.     var stat = '';
  25.     if (session_state == ss) {
  26.       stat = 'unchanged';
  27.     } else {
  28.       stat = 'changed';
  29.     }
  30.  
  31.     e.source.postMessage(stat, e.origin);
  32.   };

Télécharger

L’état du navigateur OP sera généralement stocké dans un cookie ou dans un stockage local HTML5. C’est l’origine liée au serveur d’autorisations. Il capture des événements significatifs tels que les connexions, les déconnexions, le changement d’utilisateur, le changement de statut d’authentification pour les clients utilisés par l’utilisateur final, etc. Ainsi, l’OP DEVRAIT mettre à jour la valeur de l’état du navigateur en réponse à de tels événements significatifs. En conséquence, le prochain appel à check_session () après un tel événement renverra la valeur modifiée. Il est RECOMMANDÉ que le terminal opérateur ne mette pas à jour l’état du navigateur trop fréquemment en l’absence d’événements significatifs, afin d’éviter un trafic réseau excessif sur le client en réponse à des événements erronés.

Le calcul de l’état de session renvoyé en réponse à des demandes d’authentification infructueuses DEVRAIT, en plus de l’état du navigateur, incorporer suffisamment d’aléa sous la forme d’un sel afin d’empêcher l’identification d’un utilisateur final lors d’appels successifs au point de terminaison d’autorisation de l’OP.

Dans le cas d’un client autorisé (réponse d’authentification réussie), l’OP DEVRAIT changer la valeur de l’état de session renvoyé au client dans l’un des événements suivants :

- L’ensemble des utilisateurs authentifiés auprès du navigateur change (connexion, déconnexion, ajout de session).
- Statut d’authentification des clients utilisés par les modifications apportées à l’utilisateur final.

De plus, l’état du navigateur utilisé pour vérifier l’état de la session DEVRAIT changer avec de tels événements. Les appels à check_session() renverront les modifications apportées par rapport aux versions antérieures de l’état de session après de tels événements. Il est RECOMMANDÉ que l’état du navigateur NE DEVRAIT PAS varier trop souvent en l’absence de tels événements afin de minimiser le trafic sur le réseau causé par la réponse du client aux notifications modifiées.

Dans le cas d’une demande d’authentification infructueuse, la valeur de l’état de session renvoyée DEVRAIT varier avec chaque demande. Cependant, l’état de la session du navigateur n’a pas besoin de changer sauf si un événement significatif se produit. En particulier, de nombreuses valeurs d’état de session peuvent être simultanément valides, par exemple en introduisant un sel aléatoire dans les états de session émis en réponse à des demandes d’authentification infructueuses.

Si un cookie est utilisé pour conserver l’état du navigateur OP, l’indicateur HttpOnly ne peut probablement pas être défini pour ce cookie car il doit être accessible à partir de JavaScript. Par conséquent, les informations pouvant être utilisées pour identifier l’utilisateur ne doivent pas être placées dans le cookie, car elles pourraient être lues par du JavaScript non associé.

Dans certaines implémentations, les notifications modifiées ne se produiront que lorsque la session de l’utilisateur final sera modifiée, alors que dans d’autres implémentations, elles pourront également survenir à la suite de modifications apportées à d’autres sessions entre l’agent d’utilisateur et le terminal opérateur. Les PR doivent être préparés à toute éventualité, en gérant en silence tous les faux positifs susceptibles de se produire.

L’état du navigateur OP sera généralement stocké dans un cookie ou dans un stockage local HTML5. C’est l’origine liée au serveur d’autorisations. Il capture des événements significatifs tels que les connexions, les déconnexions, le changement d’utilisateur, le changement de statut d’authentification pour les clients utilisés par l’utilisateur final, etc. Ainsi, l’OP DEVRAIT mettre à jour la valeur de l’état du navigateur en réponse à de tels événements significatifs. En conséquence, le prochain appel à check_session () après un tel événement renverra la valeur modifiée. Il est RECOMMANDÉ que le terminal opérateur ne mette pas à jour l’état du navigateur trop fréquemment en l’absence d’événements significatifs, afin d’éviter un trafic réseau excessif sur le client en réponse à des événements erronés.

Le calcul de l’état de session renvoyé en réponse à des demandes d’authentification infructueuses DEVRAIT, en plus de l’état du navigateur, incorporer suffisamment d’aléa sous la forme d’un sel afin d’empêcher l’identification d’un utilisateur final lors d’appels successifs au point de terminaison d’autorisation de l’OP.

Dans le cas d’un client autorisé (réponse d’authentification réussie), l’OP DEVRAIT changer la valeur de l’état de session renvoyé au client dans l’un des événements suivants :
- L’ensemble des utilisateurs authentifiés auprès du navigateur change (connexion, déconnexion, ajout de session).
- Statut d’authentification des clients utilisés par les modifications apportées à l’utilisateur final.

De plus, l’état du navigateur utilisé pour vérifier l’état de la session DEVRAIT changer avec de tels événements. Les appels à check_session () renverront les modifications apportées par rapport aux versions antérieures de l’état de session après de tels événements. Il est RECOMMANDÉ que l’état du navigateur NE DEVRAIT PAS varier trop souvent en l’absence de tels événements afin de minimiser le trafic sur le réseau causé par la réponse du client aux notifications modifiées.

Dans le cas d’une demande d’authentification infructueuse, la valeur de l’état de session renvoyée DOIT varier avec chaque demande. Cependant, l’état de la session du navigateur n’a pas besoin de changer sauf si un événement significatif se produit. En particulier, de nombreuses valeurs d’état de session peuvent être simultanément valides, par exemple en introduisant un sel aléatoire dans les états de session émis en réponse à des demandes d’authentification infructueuses.

Si un cookie est utilisé pour conserver l’état du navigateur OP, l’indicateur HttpOnly ne peut probablement pas être défini pour ce cookie car il doit être accessible à partir de JavaScript. Par conséquent, les informations pouvant être utilisées pour identifier l’utilisateur ne doivent pas être placées dans le cookie, car elles pourraient être lues par du JavaScript non associé.

Dans certaines implémentations, les notifications modifiées ne se produiront que lorsque la session de l’utilisateur final sera modifiée, alors que dans d’autres implémentations, elles pourront également survenir à la suite de modifications apportées à d’autres sessions entre l’agent d’utilisateur et le terminal opérateur. Les PR doivent être préparés à toute éventualité, en gérant en silence tous les faux positifs susceptibles de se produire.

5. Déconnexion initiée par le RP

Un RP peut notifier à l’OP que l’utilisateur final s’est déconnecté du site et peut également vouloir se déconnecter de l’OP. Dans ce cas, le RP, après avoir déconnecté l’utilisateur final du RP, redirige l’agent utilisateur de l’utilisateur final vers l’URL du point de terminaison de déconnexion de l’OP. Cette URL est normalement obtenue via l’élément end_session_endpoint de la réponse à la découverte de l’OP ou peut être apprise via d’autres mécanismes.

Cette spécification définit également les paramètres suivants qui sont transmis en tant que paramètres de requête dans la demande de déconnexion :

id_token_hint

CONSEILLÉ. Le jeton d’ID précédemment émis a été transmis au point de terminaison de la déconnexion en tant qu’indication de la session authentifiée actuelle de l’utilisateur final avec le client. Ceci est utilisé comme indication de l’identité de l’utilisateur final que le RP demande à être déconnecté par l’OP. L’OP n’a pas besoin d’être répertorié en tant que public du jeton ID lorsqu’il est utilisé en tant que valeur id_token_hint.

post_logout_redirect_uri

OPTIONNEL. URL à laquelle le RP demande que l’agent utilisateur de l’utilisateur final soit redirigé après la déconnexion. La valeur DOIT avoir déjà été enregistrée auprès de l’OP, soit à l’aide du paramètre d’enregistrement post_logout_redirect_uris, soit via un autre mécanisme. S’il est fourni, l’OP DEVRAIT honorer cette demande après la déconnexion.

state

OPTIONNEL. Valeur opaque utilisée par le RP pour maintenir l’état entre la demande de déconnexion et le rappel au noeud final spécifié par le paramètre de requête post_logout_redirect_uri. S’il est inclus dans la demande de déconnexion, l’OP retransmet cette valeur au RP en utilisant le paramètre de requête d’état lors de la redirection de l’agent d’utilisateur vers le RP.

Au point de terminaison de la déconnexion, l’OP DEVRAIT demander à l’utilisateur final s’il souhaite également se déconnecter de l’OP. Si l’utilisateur final dit "oui", alors l’OP DOIT déconnecter l’utilisateur final.

5.1. Redirection vers RP après la déconnexion

Dans certains cas, le RP demande à ce que l’agent d’utilisateur de l’utilisateur final soit redirigé vers le RP après la déconnexion. La redirection post-déconnexion n’est effectuée que lorsque la déconnexion est lancée par le RP, auquel cas la cible de la redirection est la valeur du paramètre de requête post_logout_redirect_uri utilisée par le RP initiateur ; sinon ce n’est pas fait. Cette spécification définit ce paramètre d’enregistrement dynamique à cette fin, conformément à la section 2.1 d’OpenID Connect Dynamic Client Registration 1.0 [OpenID.Registration].

5.1.1. Métadonnées d’inscription du client

Ce paramètre de métadonnées client PEUT être inclus dans les informations d’enregistrement du client lorsque la gestion de session et l’enregistrement dynamique sont pris en charge :

post_logout_redirect_uris

OPTIONNEL. Tableau d’URL fournies par le RP auquel il PEUT demander que l’agent d’utilisateur de l’utilisateur final soit redirigé à l’aide du paramètre post_logout_redirect_uri après la déconnexion.

6. Validation

Si l’une des procédures de validation définies dans la présente spécification échoue, les opérations nécessitant des informations qui n’ont pas été correctement validées DOIVENT être annulées et les informations qui n’ont pas été validées NE DOIVENT PAS être utilisées.

7. Considérations de mise en œuvre

Cette spécification définit les fonctionnalités utilisées par les parties utilisatrices et les fournisseurs OpenID ayant choisi d’implémenter la gestion de session. Toutes ces parties utilisatrices et fournisseurs OpenID DOIVENT implémenter les fonctions listées dans cette spécification comme étant "REQUIRED" ou décrites avec un "MUST". Aucune autre considération d’implémentation pour les implémentations de Session Management n’est définie par cette spécification.

8. Considérations de sécurité

L’OP iframe DOIT imposer que l’appelant ait la même origine que son cadre parent. Il DOIT rejeter les demandes postMessage provenant de toute autre source, afin d’empêcher les attaques de script entre sites.

Le paramètre id_token_hint associé à une demande de déconnexion peut être utilisé pour déterminer quel RP a lancé la demande de déconnexion. Les demandes de déconnexion sans valeur id_token_hint valide constituent un moyen de déni de service potentiel ; par conséquent, les opérateurs peuvent vouloir demander une confirmation explicite de l’utilisateur avant d’agir.

...

Notes

[1Ce n’est pas DnC qui la propose ! Nous sommes d’ailleurs au recul par rapport à celle-ci.

[2Cette solution a notre préférence, voir la note suivante.

[3Il est donc inexact d’affirmer, comme indiqué au paragraphe précédent, que la connaissance de l’état de session peut être effectué "sans générer de trafic sur le réseau". Pour réduire le trafic, il conviendra d’augmenter l’intervalle d’interrogation de l’OP. Chez DnC, nous préférons répéter la demande d’authentification avec prompt = none. Pour ce faire, notre système SLI opérant la reconnexion de façon transparente, il suffira de configurer les sessions locales aux applications clientes avec une durée de vie courte.

Offline Access

  publié le par DnC

Dans le cadre du flux Authorization Code d’OpenID Connect, "offline_access" est défini comme valeur de portée (scope) dans une demande d’autorisation. L’objectif serait de permettre à une application d’effectuer des opérations au nom d’un utilisateur final alors que celui-ci est hors ligne.

Ceci mérite un éclaircissement.

Spécification de la demande "offline_access"

Traduction du document OpenID Connect Core 1.0 incorporating errata set 1 § 11 :

11. Accès hors ligne
OpenID Connect définit la valeur de portée suivante pour demander un accès hors ligne :

offline_access

OPTIONNEL. Cette valeur de portée demande qu’un jeton d’actualisation OAuth 2.0 soit émis et puisse être utilisé pour obtenir un jeton d’accès qui accorde un accès au point de terminaison UserInfo [1] de l’utilisateur final même lorsque l’utilisateur final n’est pas présent (non connecté).

Lorsqu’un accès hors connexion est demandé, une valeur de paramètre d’invite de consentement DOIT être utilisée [2], sauf si d’autres conditions de traitement de la demande permettant un accès hors connexion aux ressources demandées sont en place. L’OP DOIT toujours obtenir l’autorisation de renvoyer un jeton d’actualisation permettant un accès hors ligne aux ressources demandées. Un consentement de l’utilisateur préalablement enregistré ne suffit pas toujours pour accorder un accès hors connexion [3].

À la réception d’un paramètre d’étendue contenant la valeur offline_access, le serveur d’autorisations :

- DOIT s’assurer que le paramètre prompt contient un consentement sauf si d’autres conditions de traitement de la demande permettant un accès hors ligne aux ressources demandées sont en place ; sauf si l’une de ces conditions ou les deux sont remplies, il DOIT alors ignorer la demande offline_access,
- DOIT ignorer la demande offline_access sauf si le client utilise une valeur response_type qui entraînerait le renvoi d’un code d’autorisation,
- DOIT explicitement recevoir ou avoir le consentement de tous les clients lorsque le type d’application enregistré est Web,
- DEVRAIT recevoir explicitement ou avoir le consentement de tous les clients lorsque le type d’application enregistré est natif.

L’utilisation des jetons d’actualisation n’est pas exclusive au cas d’utilisation offline_access. Le serveur d’autorisations PEUT accorder des jetons d’actualisation dans d’autres contextes qui ne relèvent pas de la portée de cette spécification [4].

Cas d’usage

L’objectif est de permettre à une application d’effectuer des opérations au nom d’un utilisateur final alors que celui-ci est hors ligne (le jeton d’accès n’est plus valide).

Un exemple est une sauvegarde périodique de certaines données chaque nuit.

Lors de la connexion, l’application cliente demandera le consentement de l’utilisateur en appelant le contrôleur Authorize avec la valeur ’offline_access’ dans le paramètre scope.

L’application devra enregistrer le jeton de rafraîchissement obtenu de façon persistante pour l’utiliser ultérieurement, même si l’utilisateur est déconnecté.

OAuthSD accorde une acception générale au jeton d’actualisation et en assure un contrôle serré

Certaines implémentations distinguent le jeton d’actualisation obtenu avec le consentement de l’utilisateur en réponse à la demande "offline_access" du jeton d’actualisation "ordinaire" (fourni sans ce consentement). Il est alors nommé "jeton hors ligne".

Nous ne voyons rien de tel dans les spécifications ni dans les tests de certification. Nous nous en tiendrons à l’interprétation de Brent Shaffer matérialisée dans la librairie OAuth 2.0 Server PHP :

Les demandes OpenID Connect incluent le jeton d’actualisation (refresh token) uniquement si la portée offline_access a été demandée et accordée.

Autrement dit : dans le cadre d’OpenID Connect, le jeton d’actualisation a une portée générale. Qu’il soit utilisé pour un scénario "hors ligne" ou pour tout autre usage ne dépend que du concepteur de l’application [5].

Bonnes pratiques pour la sécurité

Nous sommes d’avis que permettre l’actualisation du jeton d’accès affaiblit la sécurité des flux OpenID Connect (comme toute forme de délivrance de jeton en dehors du flux d’autorisation avec code).
Si vous mettez en œuvre le jeton d’actualisation, voyez les recommandations de cet article : Rafraîchir (actualiser) un jeton d’accès.

Notes

[1Seulement UserInfo ? L’examen de différents codes et documentations montre qu’un jeton retourné n’a pas un usage prédéfini. Un point à éclaircir.

[2Autrement dit un appel à Authorize avec prompt=consent.

[3C’est assez curieux : on voit mal comment une application "hors connexion" pourrait demander le consentement de l’utilisateur sans avoir pré-enregistré ce consentement quand l’utilisateur était connecté à l’application.

[4La sécurité requiert de contrôler l’usage du jeton d’actualisation, OAuthSD ne fournit ce jeton que si offline_access a été demandé.

[5On retrouve le principe général selon lequel la signification d’une portée d’autorisation (scope) ne dépend que de l’usage qui en est fait par l’application.