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:
- 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).
- 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". - 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:
- 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).
- 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.