Home > OpenID Connect OAuth Server dedicated > Develop > OpenID Connect > Monitoring the state of authentication and Single Log Out (SLO)

Monitoring the state of authentication and Single Log Out (SLO)

The purpose of the authentication status monitoring is to synchronize the local connection of an application with the corresponding access token.

Introduction

OAuthSD, following OAuth 2.0, considers that the end user is connected to an application as long as the associated access token is valid.

There is no "naturally" direct relationship between this token and the local connection state of an application. Each application will have to set up a monitoring, client side, in order to:
- monitor the validity of the access token,
- complete the Single Logout (SLO) mechanism by causing local disconnection of the application when the access token has expired.

1. The community is offered a "standard" for managing the OpenID Connect session (see: OpenID Connect Session Management ) which relies on iframes on the server side and on the application side.

In version 30 published in August 2020, a section disappears that was present in version 28 and was entitled "Back-Channel Logout".
Indeed, a new proposal "OpenID Connect Back-Channel Logout" avoid the malfunctions that were mentionned in paragraph "User Agents Blocking Access to Third-Party Content":

Note that at the time of this writing, some User Agents (browsers) are starting to block access to third-party content by default to block some mechanisms used to track the End-User’s activity across sites. Specifically, the third-party content being blocked is website content with an origin different that the origin of the focused User Agent window. Site data includes cookies and any web storage APIs (sessionStorage, localStorage, etc.).

This can prevent the ability for notifications from the OP at the RP from being able to access the RP’s User Agent state to implement local logout actions. In particular, cookies and web storage APIs may not be available in the OP frame loaded in the RP context. The side effect here is that, depending on the used mechanism (cookies or web storage), the data needed to recalculate session_state might not be available. Cookie based implementations might then return changed for every single call, resulting in infinite loops of re-authentications. Therefore, deployments of this specification are recommended to include defensive code to detect this situation, and if possible, notify the End-User that the requested RP logouts could not be performed. The details of the defensive code needed are beyond the scope of this specification ; it may vary per User Agent and may vary over time, as the User Agent tracking prevention situation is fluid and continues to evolve. ".

A specification proposal which has undergone 30 modifications but which has not changed since August 2020, and which also ends with a finding of malfunction, is dead!

Moreover, we think (maybe wrongly?) that a "standard" for a protocol-level ISO layer should not impose a process on a lower-level layer.

2. Our solution presented here: Implementation of monitoring with Javascript: examples for SPIP and WordPress therefore interrogate the Authorize controller with the parameter display = "none".
A client-side implementation based on the OpenID Connect standard as it currently exists is much preferable to a further complication of client applications and helps avoid violation of the principle of ISO layer independence.

In support of this statement, we present monitoring methods implemented for SPIP and WordPress:

Implementation of the monitoring in the OpenID Connect client plugin for SPIP

The oa.dnc.global site uses this plugin (it is OIDC client, the snake is biting its tail!). You can see a label at the top left of the window that reflects the status of the access token linked to the current connection:

icon Meaning
The end user connection is not established with the OIDC server. Note that the user may have logged in to the application using the classic SPIP login method.
The end user and the application are connected by OIDC.
General error, unfortunately translates multiple situations:
- loss of Internet communication,
- default configuration of the plugin,
- failure to register the client application on the OAuthSD server,
- registration of the application on another server, not compatible with OAuthSD,
- unknown login of the OAuthSD server (the end user is not registered on this server),
- etc.
The message may be more explicit: "authentication error", ...

The Monitoring also allows to manage the OIDC session and in particular to perform the global disconnection (SLO).
By clicking on the icon, popups appear depending on the connection status.

Example of the Javascript code (with jQuery) used for SPIP. The plugin inserts this code at the bottom of each 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.     });

Download

Implementing monitoring in a Wordpress app

  The plugin is available on Github.

The Javascript code is inserted into a hook called by the bootstrap () function of the 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

Download

Note that you must insert a div block at the bottom of the page, which is done by means of an action in the bootstrap function at the same time as the hook of the previous function:

[PHP]

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

Download