Autenticación REST y exposición de la clave API

93

He estado leyendo sobre REST y hay muchas preguntas sobre SO al respecto, así como en muchos otros sitios y blogs. Aunque nunca he visto esta pregunta específica ... por alguna razón, no puedo envolver mi mente en este concepto ...

Si estoy construyendo una API RESTful y quiero protegerla, uno de los métodos que he visto es usar un token de seguridad. Cuando he usado otras API, ha habido un token y un secreto compartido ... tiene sentido. Lo que no entiendo es que las solicitudes para una operación de servicio de descanso se realizan a través de javascript (XHR / Ajax), ¿qué es lo que impide que alguien lo detecte con algo simple como FireBug (o "ver código fuente" en el navegador) y copiar la clave de API y luego hacerse pasar por esa persona usando la clave y el secreto?

tjans
fuente
Uno de los métodos que he visto es usar un token de seguridad , realmente existen muchos métodos. Tienes un ejemplo concreto. Creo que te confundes con "REST" frente a "hacer disponible una API de JavaScript solo para usuarios registrados" (ex google maps).
PeterMmm
1
Desde que preguntaste hace casi 2 años: ¿qué usaste finalmente?
Arjan
En realidad no usé nada, estaba más tratando de envolver mi cabeza alrededor de la creación de conceptos. El comentario de PeterMmm anterior probablemente sea cierto ... todavía no he tenido la necesidad de implementar nada de esto, pero quería mejorarme ... gracias por el seguimiento.
tjans

Respuestas:

22

api el secreto no se pasa explícitamente, el secreto se usa para generar un signo de solicitud actual, en el lado del servidor, el servidor genera el signo siguiendo el mismo proceso, si los dos signos coinciden, entonces la solicitud se autentica con éxito, por lo que solo el el signo se transmite a través de la solicitud, no el secreto.

James.Xu
fuente
9
Entonces, si es solo el signo que se pasó ... ¿no está todavía expuesto en javascript ... entonces si pongo una foto parpadeante en mi página web a través de su API (llamada por javascript), y visitas mi página, no? ¿Expongo mi clave API a cualquiera que visite mi página?
tjans
6
No creo que esté haciendo mi pregunta correctamente ... probablemente parte de la razón por la que no encontré lo que estaba buscando en primer lugar. cuando hago mi llamada ajax, digamos usando jquery, tendría que incrustar la clave api en la llamada ajax para que se pase al servidor ... en ese momento, alguien puede ver la clave API. Si lo entiendo mal, ¿cómo se envía la clave API con la solicitud si no está incrustada en el script del cliente?
tjans
4
Para concluir: a las personas se les asignará un par apikey + apisecret antes de usar un openapi / restapi, el signo apikey + se transferirá al servidor para asegurarse de que el servidor sepa quién está haciendo la solicitud, el apisecret nunca se transferirá al servidor por seguridad .
James.Xu
7
¡Entonces la declaración de @ James.Xu de que 'el secreto se usa para generar un signo de solicitud actual' es FALSO! Porque el cliente no conoce el secreto, porque no sería seguro enviárselo (¿y de qué otra manera podría saberlo?) El 'secreto', que técnicamente es una 'clave privada', lo usa SOLO EL SERVIDOR (porque nadie más lo sabe) para generar un signo que se compare con el signo del cliente. Entonces la pregunta: ¿Qué tipo de datos se están combinando con la 'clave api' que nadie más conoce más allá del cliente y el servidor? Sign = api_key + ¿qué?
AC
1
Tienes razón, @ACs. Incluso si ambos servidores (el sitio web y la API de terceros) conocen el mismo secreto, no se puede calcular alguna firma en el servidor del sitio web y luego poner ese resultado en el HTML / JavaScript, y luego hacer que el navegador lo pase a la API. Al hacerlo, cualquier otro servidor podría solicitar ese HTML del primer servidor web, obtener la firma de la respuesta y usarla en el HTML de su propio sitio web. (Realmente creo que la publicación anterior no responde a la pregunta sobre cómo una clave API pública en el HTML puede ser segura).
Arjan
61

Estamos exponiendo una API que los socios solo pueden usar en los dominios que han registrado con nosotros. Su contenido es parcialmente público (pero preferiblemente solo para mostrarse en los dominios que conocemos), pero es principalmente privado para nuestros usuarios. Entonces:

  • Para determinar qué se muestra, nuestro usuario debe iniciar sesión con nosotros, pero esto se maneja por separado.

  • Para determinar dónde se muestran los datos, se utiliza una clave API pública para limitar el acceso a los dominios que conocemos y, sobre todo, para garantizar que los datos privados del usuario no sean vulnerables a CSRF .

Esta clave de API es visible para cualquier persona, no autenticamos a nuestro socio de ninguna otra manera y no necesitamos REFERIR . Aún así, es seguro:

  1. Cuando get-csrf-token.js?apiKey=abc123se solicita nuestro :

    1. Busque la clave abc123en la base de datos y obtenga una lista de dominios válidos para esa clave.

    2. Busque la cookie de validación CSRF. Si no existe, genere un valor aleatorio seguro y colóquelo en una cookie de sesión solo HTTP . Si la cookie existió, obtenga el valor aleatorio existente.

    3. Cree un token CSRF a partir de la clave de API y el valor aleatorio de la cookie, y fírmelo . (En lugar de mantener una lista de tokens en el servidor, estamos firmando los valores. Ambos valores serán legibles en el token firmado, eso está bien).

    4. Configure la respuesta para que no se almacene en caché, agregue la cookie y devuelva un script como:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }

    Notas:

    • Lo anterior no evita que un script del lado del servidor falsifique una solicitud, sino que solo garantiza que el dominio coincida si lo solicita un navegador.

    • La misma política de origen para JavaScript garantiza que un navegador no pueda usar XHR (Ajax) para cargar y luego inspeccionar la fuente de JavaScript. En cambio, un navegador normal solo puede cargarlo usando <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(o un equivalente dinámico), y luego ejecutará el código. Por supuesto, su servidor no debe admitir el uso compartido de recursos entre orígenes ni JSONP para el JavaScript generado.

    • Un script del navegador puede cambiar el valor de document.domainantes de cargar el script anterior. Pero la misma política de origen solo permite acortar el dominio eliminando prefijos, como reescribir subdomain.example.coma solo example.com, o myblog.wordpress.coma wordpress.com, o en algunos navegadores incluso bbc.co.uka co.uk.

    • Si el archivo JavaScript se obtiene mediante algún script del lado del servidor, el servidor también obtendrá la cookie. Sin embargo, un servidor de terceros no puede hacer que el navegador de un usuario asocie esa cookie a nuestro dominio. Por lo tanto, un token CSRF y una cookie de validación que se han obtenido mediante un script del lado del servidor solo se pueden usar en llamadas posteriores del lado del servidor, no en un navegador. Sin embargo, dichas llamadas del lado del servidor nunca incluirán la cookie del usuario y, por lo tanto, solo pueden obtener datos públicos. Estos son los mismos datos que un script del lado del servidor podría extraer directamente del sitio web del socio.

  2. Cuando un usuario inicia sesión, configure alguna cookie de usuario de la forma que desee. (Es posible que el usuario ya haya iniciado sesión antes de que se solicitara JavaScript).

  3. Todas las solicitudes de API posteriores al servidor (incluidas las solicitudes GET y JSONP) deben incluir el token CSRF, la cookie de validación CSRF y (si está conectado) la cookie del usuario. El servidor ahora puede determinar si la solicitud es confiable:

    1. La presencia de un token CSRF válido garantiza que JavaScript se cargó desde el dominio esperado, si lo cargó un navegador.

    2. La presencia del token CSRF sin la cookie de validación indica una falsificación.

    3. La presencia tanto del token CSRF como de la cookie de validación CSRF no asegura nada: esto podría ser una solicitud del lado del servidor falsificada o una solicitud válida de un navegador. (No puede ser una solicitud de un navegador realizada desde un dominio no admitido).

    4. La presencia de la cookie del usuario asegura que el usuario esté conectado, pero no asegura que el usuario sea miembro del socio dado, ni que el usuario esté viendo el sitio web correcto.

    5. La presencia de la cookie del usuario sin la cookie de validación CSRF indica una falsificación.

    6. La presencia de la cookie del usuario garantiza que la solicitud actual se realice a través de un navegador. (Suponiendo que un usuario no ingrese sus credenciales en un sitio web desconocido, y asumiendo que no nos importa que los usuarios usen sus propias credenciales para realizar alguna solicitud del lado del servidor). Si también tenemos la cookie de validación CSRF, entonces esa cookie de validación CSRF era también se recibió mediante un navegador. A continuación, si también tenemos un token CSRF con una firma válida, yel número aleatorio en la cookie de validación CSRF coincide con el de ese token CSRF, luego el JavaScript para ese token también se recibió durante esa misma solicitud anterior durante la cual se configuró la cookie CSRF, por lo que también se usó un navegador. Esto también implica que el código JavaScript anterior se ejecutó antes de que se estableciera el token, y que en ese momento el dominio era válido para la clave API dada.

      Entonces: el servidor ahora puede usar de manera segura la clave API del token firmado.

    7. Si en algún momento el servidor no confía en la solicitud, se devuelve un 403 Forbidden. El widget puede responder a eso mostrando una advertencia al usuario.

No es necesario firmar la cookie de validación CSRF, ya que la estamos comparando con el token CSRF firmado. No firmar la cookie hace que cada solicitud HTTP sea más corta y la validación del servidor sea un poco más rápida.

El token CSRF generado es válido indefinidamente, pero solo en combinación con la cookie de validación, de manera efectiva hasta que se cierra el navegador.

Podríamos limitar la vida útil de la firma del token. Podríamos eliminar la cookie de validación CSRF cuando el usuario cierra la sesión, para cumplir con la recomendación de OWASP . Y para no compartir el número aleatorio por usuario entre varios socios, se podría agregar la clave API al nombre de la cookie. Pero incluso entonces no se puede actualizar fácilmente la cookie de validación CSRF cuando se solicita un nuevo token, ya que los usuarios pueden estar navegando por el mismo sitio en varias ventanas, compartiendo una sola cookie (que, al actualizar, se actualizaría en todas las ventanas, después de lo cual el El token de JavaScript en las otras ventanas ya no coincidiría con esa única cookie).

Para aquellos que usan OAuth, consulte también OAuth y Widgets del lado del cliente , de donde obtuve la idea de JavaScript. Para el uso de la API en el lado del servidor , en el que no podemos confiar en el código JavaScript para limitar el dominio, estamos usando claves secretas en lugar de las claves públicas de la API.

Arjan
fuente
1
Al usar CORS, tal vez uno pueda extender eso de manera segura. En lugar de lo anterior, cuando se maneja una OPTIONSsolicitud previa al vuelo con alguna clave API pública en la URL, el servidor puede indicarle a un navegador qué dominios están permitidos (o cancelar la solicitud). Sin embargo, tenga en cuenta que algunas solicitudes no requieren una solicitud previa al vuelo, o no usarán CORS en absoluto , y que CORS necesita IE8 +. Si se usa algún respaldo de Flash para IE7, entonces quizás alguna dinámica crossdomain.xmlpueda ayudar a lograr lo mismo para eso. Todavía no hemos probado CORS / Flash.
Arjan
10

Esta pregunta tiene una respuesta aceptada, pero solo para aclarar, la autenticación secreta compartida funciona así:

  1. El cliente tiene una clave pública, esta se puede compartir con cualquier persona, no importa, por lo que puede incrustarla en JavaScript. Se utiliza para identificar al usuario en el servidor.
  2. El servidor tiene una clave secreta y este secreto DEBE estar protegido. Por lo tanto, la autenticación de clave compartida requiere que pueda proteger su clave secreta. Por lo tanto, un cliente javascript público que se conecta directamente a otro servicio no es posible porque necesita un intermediario del servidor para proteger el secreto.
  3. El servidor firma la solicitud utilizando algún algoritmo que incluye la clave secreta (la clave secreta es como una sal) y, preferiblemente, una marca de tiempo envía la solicitud al servicio. La marca de tiempo es para evitar ataques de "repetición". La firma de una solicitud solo es válida durante unos n segundos. Puede verificarlo en el servidor obteniendo el encabezado de la marca de tiempo que debe contener el valor de la marca de tiempo que se incluyó en la firma. Si esa marca de tiempo expira, la solicitud falla.
  4. El servicio recibe la solicitud que contiene no solo la firma, sino también todos los campos que se firmaron en texto sin formato.
  5. Luego, el servicio firma la solicitud de la misma manera utilizando la clave secreta compartida y compara las firmas.
chris
fuente
Es cierto, pero por diseño, su respuesta no expone la clave API. Sin embargo, en algunas API, la clave API es visible públicamente, y de eso se trataba la pregunta: "solicitudes a una operación de servicio de descanso [...] realizadas a través de javascript (XHR / Ajax)" . (La respuesta aceptada es incorrecta sobre eso también, creo; su punto 2 es claro al respecto, bien).
Arjan
1

Supongo que te refieres a la clave de sesión, no a la clave API. Ese problema se hereda del protocolo http y se conoce como secuestro de sesión . La "solución alternativa" normal es, como en cualquier sitio web, cambiar a https.

Para ejecutar el servicio REST de forma segura, debe habilitar https y probablemente la autenticación del cliente. Pero después de todo, esto está más allá de la idea REST. REST nunca habla de seguridad.

PeterMmm
fuente
8
De hecho, me refería a la clave. Si recuerdo correctamente, para usar una API, está pasando la clave de API y el secreto al servicio de resto para que se autentique, ¿correcto? Sé que una vez que se pasa por el cable, sería cifrado por SSL, pero antes de que se envíe, es perfectamente visible por el código de cliente que lo usa ...
tjans
1

Lo que desea hacer en el lado del servidor es generar una identificación de sesión que expira que se envía al cliente al iniciar sesión o registrarse. Luego, el cliente puede usar esa identificación de sesión como un secreto compartido para firmar solicitudes posteriores.

La identificación de sesión solo se pasa una vez y DEBE ser a través de SSL.

Ver ejemplo aquí

Utilice un nonce y una marca de tiempo al firmar la solicitud para evitar el secuestro de la sesión.

Iain Porter
fuente
1
Pero, ¿cómo puede haber un inicio de sesión cuando un tercero usa su API? Si el usuario va a iniciar sesión, entonces las cosas son fáciles: ¿solo usar una sesión? Pero cuando otros sitios web necesitan autenticarse en su API, eso no ayuda. (Además, esto huele mucho a promocionar su blog.)
Arjan
1

Intentaré responder la pregunta en su contexto original. Entonces, la pregunta es "¿Es seguro colocar la clave secreta (API) en JavaScript?

En mi opinión, es muy inseguro ya que frustra el propósito de autenticación entre los sistemas. Dado que la clave estará expuesta al usuario, el usuario puede recuperar información para la que no está autorizado. Porque en una comunicación de reposo típica, la autenticación solo se basa en la clave API.

En mi opinión, una solución es que la llamada de JavaScript esencialmente pasa la solicitud a un componente del servidor interno que es responsable de hacer una llamada de descanso. El componente del servidor interno, digamos que un Servlet leerá la clave API de una fuente segura, como un sistema de archivos basado en permisos, la insertará en el encabezado HTTP y realizará la llamada de descanso externa.

Espero que esto ayude.

Desarrollador MG
fuente