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.