Mod-PAPI, AJAX y CORS

Tanto el AS-PAPI de la UV como el mod_papi intentan soportar el CORS para que las peticiones AJAX puedan renovar automaticamente la sesión si necesario (y siempre que la sesión SSO siga abierta).

CONCRETANDO

Para que las peticiones AJAX puedan renovar sesión PAPI (mod_papi) en caso de que ésta caduque es necesario:

  1. Que la aplicación efectúe todas las llamadas AJAX con la opción de "enviar credenciales" (cookies) (withCredentials: true) (ver detalles más abajo).
  2. Que se use una versión del mod_papi que envía las cabeceras CORS necesarias cuando se reproduce la petición interrumpida por la renovación (versión mod_papi > 0.0.2-19 / 21 Mar 2023).

    ...o se añada desde la aplicación las cabeceras CORS y se atienda a las peticiones OPTIONS (CORS prefligth), desactivando la gestión del mod_papi con "PAPISetCORSHeaders Off".
  3. Que el AS envíe las cabeceras CORS adecuadas (Hecho) y que el AS sepa atender a una petición "CORS prefligth / OPTIONS" (Hecho).

GESTIÓN DE ERRORES EN PETICIONES AJAX

En general toda aplicación con llamadas AJAX debe gestionar posibles errores que se puedan producir al hacer la llamada y debe habilitar un procedimiento para avisar a los usuarios de que algo ha ido mal y qué acción deben tomar.

En concreto, si la aplicación impone una caducidad a la sesión, hay que prever que la sesión haya caducado al hacer la llamada AJAX y avisar al usuario de que debe recargar página o volver al inicio y reautenticarse u otra acción.

RENOVACIÓN CON AJAX Y MOD_PAPI

En el caso de renovación de sesión dentro de una petición AJAX puede ocurrir:

  1. Que la sesión esté caducada también en el AS: en ese caso no hay nada que hacer: SI EL CORS DEL AS FUNCIONA (lo hace) la petición AJAX va a devolver el formulario de Login del IDP/AS-PAPI en vez de los datos.
    Posible resolución: todas las peticiones AJAX comprueban el tipo de dato devuelto y si content-type="text/html" (caso del Login) sacan un pop-up avisando de que el usuario debe recargar la página (o volver al inicio).
  2. Que la sesión esté activa en el AS: en ese caso SIEMPRE QUE FUNCIONE EL CORS, la renovación será transparente para el usuario.

NOTA:

Si no se resuelve el problema del CORS en la aplicación (mod_papi con opciones CORS activas y llamadas AJAX con withCredentials) y puesto que el AS envía las cabeceras CORS y, por lo tanto, el formulario de login va a llegar al AJAX incluso en este caso, siempre es posible sin más aplicar 1.

La diferencia para el usuario es que la sesión caducará más a menudo (o no, dependiendo de los timeouts de sesión configurados en el mod_papi).

INTERIORIDADES MOD_PAPI, AJAX Y CORS

  Para entender el problema con las renovaciones
  de una petición AJAX afectada por el CORS hay
  que estudiar cada una de las peticiones que
  hace el navegador cuando tiene que renovar la
  sesión dentro de la petición AJAX.

  Pongamos como ejemplo al servicio "traductor.uv.es".

  1) Petición inicial a "traductor.uv.es": funciona
    normalmente, el mod_papi detecta que la sesión ha caducado
    y tras almacenar los datos de la petición, reenvía
    a "as.uv.es" para comprobarla.

  2) Petición de comprobación a "as.uv.es":

    - Si as.uv.es NO devuelve las cabeceras CORS, -el
      navegador- ABORTA la petición AJAX, mostrando
      sólo un error en la consola del navegador y
      devolviendo un contenido vacío.

      ==> Esto es lo que os ocurría hasta ahora.

    - Para que la petición siga al siguiente paso,
      el as.uv.es tiene que devolver las cabeceras
      CORS para autorizarlo.

      ............. Cabeceras CORS ..................
        Access-Control-Allow-Origin: $corsreqorigin
        Access-Control-Allow-Credentials: true
        Vary: Origin

      Además, para el caso de "CORS prefligth", si el método
      es OPTIONS debe devolver:

        Access-Control-Allow-Methods: POST, GET, OPTIONS
        Access-Control-Allow-Headers: $corsreqheaders

      donde "corsreqorigin" es el contenido de la
      cabecera "Origin" y "corsreqheaders" el contenido
      de la cabecera "Access-Control-Request-Headers"
      que vienen en la petición HTML correspondiente.
      ............................................

      ==> Actualmente, el AS -YA DEVUELVE- las cabeceras
          CORS correctas.

   - Para que "as.uv.es" pueda comprobar la sesión le
     DEBEN llegar sus cookies de sesión. Por defecto
     en las peticiones AJAX cross-domain -NO- se
     envían los cookies.
   - Para que los cookies se envíen en la petición AJAX
     es NECESARIO que el programa javascript active
     la opción "withCredentials":

        const xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://example.com/', true);
        xhr.withCredentials = true;

     En jquery (en general, aunque se puede poner en cada
     petición):

      $.ajaxSetup({
        crossDomain: true,
        xhrFields: {
            withCredentials: true
            }
         });

      ===> HAY que asegurarse de que todas las peticiones
           posiblemente afectadas por la renovación lleven
           esta opción.

   - Si -no- se pone esta opción en los XMLHttpRequest,
     "as.uv.es" no recibirá los cookies y al no encontrar
     la sesión abierta devolverá EL FORMULARIO DE LOGIN.

  3) El mod_papi de traductor.uv.es recibe la confirmación
     de la renovación y regenera la petición original
     mediante CURL y devuelve el resultado al navegador.

     - Si en esta petición no se devuelven también
       las cabeceras CORS, el navegador -otra vez- aborta
       la petición. Esto aunque la petición sea para
       traductor.uv.es, no es cross-domain, ya que existe
       la condición de que TODAS LAS REDIRECCIONES
       DENTRO DE UNA PETICIÓN CORS DEBEN RESPONDER
       CON CORS.

     - Las cabeceras CORS a devolver son las mismas que
       en el caso de as.uv.es. PERO el contenido de la
       cabecera "Origin" es "null" (NO p.e.
       https://traductor.uv.es ; porque se entiende que
       es una petición "interna"). En este caso hay que devolver
       la cadena "null" en "Access-Control-Allow-Origin".

       [NOTA: el IE que quiere recibir "null"...
       aunque envía un Origin:traductor.uv.es; mod_papi
       tiene en cuenta esto y siempre devuelve "null" a menos
       que sea un CORS prefligth con método OPTIONS]

   4) En el caso de GET, en vez de usar el CURL y hacer
      la petición él mismo, el mod_papi se limita a
      redirigir al navegador al URL incial de la petición.

     - De nuevo, al contestar a esta redirección mod_papi
       debe devolver las cabeceras CORS al igual que en 3).

   5) En cualquiera de las peticiones anteriores, si el
      navegador considera que la petición AJAX es "no
      estándar" (lo que quiere decir que se define
      explícitamente cualquier cosa del XMLHttpRequest,
      como por ejemplo añadir el "content-type") o bien
      la petición es POST, el navegador ANTES de efectuar
      la petición efectiva lanza un "CORS prefligth"; es
      decir, una petición HTMP con el mismo URL pero
      con método OPTIONS en vez de POST o GET. Todos
      los intervinientes deben saber contestar a estas
      peticiones OPTIONS devolviendo las cabeceras CORS
        (
        Access-Control-Allow-Origin: $correqsorigin
        Access-Control-Allow-Credentials: true
        Vary: Origin
        Access-Control-Allow-Methods: POST, GET, OPTIONS
        Access-Control-Allow-Headers: $corsreqheaders
        )

El mod_papi ha sido modificado para que tenga en cuenta todo esto, pero sólo a partir de la versión 0.0.2-19.

Universitat de Valencia

 PAPI-SSO (Single Sign On)