Prevención del diálogo de autenticación Básico HTTP mediante Interceptores AngularJS


Estoy creando una aplicación web AngularJS (1.2.16) con una API RESTful, y me gustaría enviar respuestas no autorizadas 401 para solicitudes donde la información de autenticación no es válida o no está presente. Cuando lo hago, incluso con un interceptor HTTP presente, veo el diálogo básico "Autenticación Requerida" presentado por el navegador cuando se realiza una solicitud AJAX a través de AngularJS. Mi interceptor ejecuta después de ese diálogo, que es demasiado tarde para hacer algo útil.

Un hormigón ejemplo:

Mi API backend devuelve 401 para /api/things a menos que esté presente un token de autorización. Bonito y sencillo.

En el lado de la aplicación AngularJS, he mirado los docs y configuré un interceptor como este en el bloque config:

$httpProvider.interceptors.push(['$q', function ($q) {
  return {
    'responseError': function (rejection) {
      if (rejection.status === 401) {
        console.log('Got a 401')
      }
      return $q.reject(rejection)
    }
  }
}])

Cuando cargue mi aplicación, elimine el token de autenticación y realice una llamada AJAX a /api/things (para activar el interceptor anterior), veo esto:

Se requiere Autenticación

Si cancelo ese diálogo, veo la salida console.log de "Tengo un 401" que esperaba ver en lugar de ese diálogo :

Tengo un 401

Claramente, el interceptor está funcionando, ¡pero está interceptando demasiado tarde!

Veo numerosos mensajes en la web con respecto a la autenticación con AngularJS en situaciones como esta, y todos parecen usar interceptores HTTP, pero ninguno de ellos menciona el cuadro de diálogo básico de autenticación emergente. Algunos pensamientos erróneos que tuve para su aparición incluyen:

  • Falta Content-Type: application/json encabezado en el respuesta? No, está ahí.
  • ¿Necesita devolver algo que no sea el rechazo de la promesa? Ese código siempre se ejecuta después del diálogo, sin importar lo que se devuelva.

¿Me falta algún paso de configuración o estoy usando el interceptor incorrectamente?

Author: Collin Allen, 2014-06-10

3 answers

Lo descubrí!

El truco era enviar un encabezado de respuesta WWW-Authenticate de algún valor que no sea Basic. A continuación, puede capturar el 401 con un interceptor básico $http, o algo aún más inteligente como angular-http-auth.

 12
Author: Collin Allen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-10 18:49:22

Tuve este problema junto con Spring Boot Security (HTTP basic), y desde Angular 1.3 tienes que configurar $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; para que la ventana emergente no aparezca.

 8
Author: Busata,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-01 18:07:41

Para referencia futura

Se me ocurrió esta solución al tratar de manejar errores 401. No tenía la opción de reescribir Basic a x-Basic o algo similar, así que he decidido manejarlo en el lado del cliente con Angular.

Al iniciar una sesión, primero intente hacer una solicitud incorrecta con un usuario falso para desechar las credenciales almacenadas en caché.

Tengo esta función haciendo las peticiones (está usando el $.ajax de jquery con asynch deshabilitado llamadas):

function authenticateUser(username, hash) {
    var result = false;
    var encoded = btoa(username + ':' + hash);

    $.ajax({
        type: "POST",
        beforeSend: function (request) {
            request.setRequestHeader("Authorization", 'Basic ' + encoded);
        },
        url: "user/current",
        statusCode: {
            401: function () {
                result = false;
            },
            200: function (response) {
                result = response;
            }
        },
        async: false
    });

    return result;
}

Así que cuando intento cerrar la sesión de un usuario, esto sucede:

//This will send a request with a non-existant user.
//The purpose is to overwrite the cached data with something else
accountServices.authenticateUser('logout','logout');

//Since setting headers.common.Authorization = '' will still send some
//kind of auth data, I've redefined the headers.common object to get
//rid of the Authorization property
$http.defaults.headers.common = {Accept: "application/json, text/plain, */*"};
 1
Author: Alex Szabó,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-13 18:59:06