Autenticação REST e exposição da chave API

93

Tenho lido sobre o REST e há muitas perguntas no SO sobre isso, assim como em muitos outros sites e blogs. Embora eu nunca tenha visto essa pergunta específica ser feita ... por algum motivo, não consigo entender esse conceito ...

Se estou construindo uma API RESTful e desejo protegê-la, um dos métodos que vi é usar um token de segurança. Quando usei outras APIs, houve um token e um segredo compartilhado ... faz sentido. O que eu não entendo é que as solicitações para uma operação de serviço de repouso estão sendo feitas por meio de javascript (XHR / Ajax), o que é evitar que alguém fareje isso com algo simples como FireBug (ou "ver fonte" no navegador) e copiar a chave de API e, em seguida, personificar essa pessoa usando a chave e o segredo?

tjans
fonte
um dos métodos que vi é usar um token de segurança ; na verdade, existem muitos métodos por aí. Você tem um exemplo concreto. Posso pensar que você se confundiu com "REST" vs. "disponibilizar uma API javascript apenas para usuários registrados" (por exemplo, google maps).
PeterMmm
1
Já que você perguntou há quase 2 anos: o que você acabou de usar?
Arjan
Na verdade, não usei nada, estava mais apenas tentando envolver minha cabeça em criar os conceitos. O comentário de PeterMmm acima provavelmente é verdade ... ainda não tive a necessidade de implementar nada disso, mas eu queria me aprimorar ... obrigado pelo acompanhamento.
tjans

Respostas:

22

o segredo da API não é passado explicitamente, o segredo é usado para gerar um sinal de solicitação atual, no lado do servidor, o servidor gera o sinal seguindo o mesmo processo, se os dois sinais corresponderem, então a solicitação é autenticada com sucesso - então apenas o o sinal é passado através do pedido, não o segredo.

James.Xu
fonte
9
Então, se for apenas o sinal que foi passado ... isso ainda não está exposto em javascript ... então se eu colocar uma foto piscando em minha página da web por meio de sua API (chamada por javascript) e você visitar minha página, não t Estou expondo minha chave de API para qualquer pessoa que visita minha página?
tjans
6
Eu não acho que estou fazendo minha pergunta corretamente ... provavelmente parte da razão pela qual eu não estava encontrando o que estava procurando em primeiro lugar. quando eu faço minha chamada ajax, digamos usando jquery, eu teria que incorporar a chave api na chamada ajax para que ela seja passada para o servidor ... nesse ponto, alguém pode ver a chave API. Se estou entendendo isso errado, como a chave de API é enviada com a solicitação se não está incorporada ao script do cliente?
tjans
4
para concluir: as pessoas receberão um par apikey + apisecret antes de usar um openapi / restapi, o sinal apikey + será transferido para o lado do servidor para garantir que o servidor saiba quem está fazendo a solicitação, o apisecret nunca será transferido para o lado do servidor por segurança .
James.Xu
7
Portanto, a declaração de @ James.Xu de que 'o segredo é usado para gerar um sinal de solicitação atual' é FALSA! Porque o cliente não conhece o segredo, porque não seria seguro enviá-lo a ele (e de que outra forma ele saberia disso?) O 'segredo' que é tecnicamente uma 'chave privada' é usado SOMENTE PELO SERVIDOR (porque ninguém mais sabe) para gerar um sinal para ser comparado ao sinal do cliente. Portanto, a pergunta: Que tipo de dados estão sendo combinados com a 'chave da API' que ninguém mais conhece além do cliente e do servidor? Sign = api_key + o quê?
ACs de
1
Você está certo, @ACs. Mesmo que os dois servidores (o site e a API de terceiros) conheçam o mesmo segredo, não se pode calcular alguma assinatura no servidor do site e, em seguida, colocar esse resultado no HTML / JavaScript e, em seguida, fazer o navegador passá-lo adiante para a API. Ao fazer isso, qualquer outro servidor poderia solicitar esse HTML do primeiro servidor da web, obter a assinatura da resposta e usá-la no HTML em seu próprio site. (Eu realmente acho que a postagem acima não responde à pergunta sobre como uma chave de API pública no HTML pode ser segura.)
Arjan
61

Estamos expondo uma API que os parceiros só podem usar em domínios que eles tenham registrado conosco. Seu conteúdo é parcialmente público (mas de preferência apenas para ser mostrado nos domínios que conhecemos), mas é principalmente privado para nossos usuários. Assim:

  • Para determinar o que é mostrado, nosso usuário deve estar logado conosco, mas isso é tratado separadamente.

  • Para determinar onde os dados são mostrados, uma chave de API pública é usada para limitar o acesso aos domínios que conhecemos e, acima de tudo, para garantir que os dados privados do usuário não sejam vulneráveis ​​a CSRF .

Esta chave API é realmente visível para qualquer pessoa, não autenticamos nosso parceiro de nenhuma outra forma e não precisamos de REFERER . Ainda assim, é seguro:

  1. Quando o nosso get-csrf-token.js?apiKey=abc123é solicitado:

    1. Procure a chave abc123no banco de dados e obtenha uma lista de domínios válidos para essa chave.

    2. Procure o cookie de validação CSRF. Se não existir, gere um valor aleatório seguro e coloque-o em um cookie de sessão somente HTTP . Se o cookie existisse, obtenha o valor aleatório existente.

    3. Crie um token CSRF a partir da chave da API e o valor aleatório do cookie e assine-o . (Em vez de manter uma lista de tokens no servidor, estamos assinando os valores. Ambos os valores poderão ser lidos no token assinado, tudo bem.)

    4. Defina a resposta para não ser armazenada em cache, adicione o cookie e retorne um 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:

    • O exposto acima não impede que um script do lado do servidor falsifique uma solicitação, mas apenas garante que o domínio corresponda se solicitado por um navegador.

    • A mesma política de origem para JavaScript garante que um navegador não possa usar XHR (Ajax) para carregar e inspecionar a fonte JavaScript. Em vez disso, um navegador normal só pode carregá-lo usando <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(ou um equivalente dinâmico) e, em seguida, executará o código. Claro, seu servidor não deve suportar Compartilhamento de recursos de origem cruzada nem JSONP para o JavaScript gerado.

    • Um script de navegador pode alterar o valor de document.domainantes de carregar o script acima. Mas a mesma política de origem só permite encurtar o domínio removendo prefixos, como reescrever subdomain.example.comapenas para example.com, ou myblog.wordpress.compara wordpress.com, ou em alguns navegadores até bbc.co.ukpara co.uk.

    • Se o arquivo JavaScript for obtido usando algum script do lado do servidor, o servidor também obterá o cookie. No entanto, um servidor de terceiros não pode fazer o navegador de um usuário associar esse cookie ao nosso domínio. Portanto, um token CSRF e um cookie de validação que foram buscados usando um script do lado do servidor só podem ser usados ​​por chamadas subsequentes do lado do servidor, não em um navegador. No entanto, essas chamadas do lado do servidor nunca incluirão o cookie do usuário e, portanto, só podem buscar dados públicos. Esses são os mesmos dados que um script do lado do servidor pode extrair diretamente do site do parceiro.

  2. Quando um usuário efetua login, defina algum cookie de usuário da maneira que desejar. (O usuário pode já ter feito login antes de o JavaScript ser solicitado.)

  3. Todas as solicitações de API subsequentes para o servidor (incluindo solicitações GET e JSONP) devem incluir o token CSRF, o cookie de validação CSRF e (se conectado) o cookie do usuário. O servidor agora pode determinar se a solicitação é confiável:

    1. A presença de um token CSRF válido garante que o JavaScript foi carregado do domínio esperado, se carregado por um navegador.

    2. A presença do token CSRF sem o cookie de validação indica falsificação.

    3. A presença do token CSRF e do cookie de validação CSRF não garante nada: pode ser uma solicitação forjada do lado do servidor ou uma solicitação válida de um navegador. (Não pode ser uma solicitação de um navegador feita a partir de um domínio não compatível.)

    4. A presença do cookie do usuário garante que o usuário esteja conectado, mas não garante que o usuário seja um membro do parceiro fornecido, nem que o usuário esteja visualizando o site correto.

    5. A presença do cookie do usuário sem o cookie de validação CSRF indica falsificação.

    6. A presença do cookie do usuário garante que a solicitação atual seja feita por meio de um navegador. (Supondo que um usuário não insira suas credenciais em um site desconhecido e supondo que não nos importamos com os usuários usando suas próprias credenciais para fazer alguma solicitação do lado do servidor.) Se também tivermos o cookie de validação CSRF, então esse cookie de validação CSRF foi também recebeu usando um navegador. Em seguida, se também tivermos um token CSRF com uma assinatura válida, eo número aleatório no cookie de validação CSRF corresponde ao daquele token CSRF, então o JavaScript para esse token também foi recebido durante a mesma solicitação anterior durante a qual o cookie CSRF foi definido, portanto, usando um navegador. Isso também implica que o código JavaScript acima foi executado antes que o token fosse definido e que, naquele momento, o domínio era válido para a chave API fornecida.

      Portanto: o servidor agora pode usar com segurança a chave API do token assinado.

    7. Se em algum ponto o servidor não confiar na solicitação, um 403 Forbidden será retornado. O widget pode responder a isso mostrando um aviso ao usuário.

Não é necessário assinar o cookie de validação CSRF, pois o estamos comparando ao token CSRF assinado. Não assinar o cookie torna cada solicitação HTTP mais curta e a validação do servidor um pouco mais rápida.

O token CSRF gerado é válido indefinidamente, mas apenas em combinação com o cookie de validação, de forma eficaz até que o navegador seja fechado.

Podemos limitar o tempo de vida da assinatura do token. Poderíamos excluir o cookie de validação CSRF quando o usuário fizer logout, para atender à recomendação do OWASP . E para não compartilhar o número aleatório por usuário entre vários parceiros, pode-se adicionar a chave de API ao nome do cookie. Mas mesmo assim, não é possível atualizar facilmente o cookie de validação CSRF quando um novo token é solicitado, pois os usuários podem estar navegando no mesmo site em várias janelas, compartilhando um único cookie (que, ao ser atualizado, seria atualizado em todas as janelas, após o que o O token de JavaScript nas outras janelas não corresponderia mais a esse único cookie).

Para aqueles que usam OAuth, consulte também OAuth e Widgets do lado do cliente , de onde tive a ideia do JavaScript. Para o uso da API no lado do servidor , em que não podemos contar com o código JavaScript para limitar o domínio, estamos usando chaves secretas em vez das chaves API públicas.

Arjan
fonte
1
Ao usar o CORS, talvez seja possível estendê-lo com segurança. Em vez do acima, ao lidar com uma OPTIONSsolicitação pré-veiculada com alguma chave de API pública na URL, o servidor pode informar a um navegador quais domínios são permitidos (ou cancelar a solicitação). Esteja ciente de que algumas solicitações não exigem uma solicitação pré-veiculada ou não usarão CORS de forma alguma , e que o CORS precisa do IE8 +. Se algum substituto do Flash for usado para o IE7, talvez alguma dinâmica crossdomain.xmlpossa ajudar a conseguir o mesmo por isso. Ainda não experimentamos CORS / Flash.
Arjan
10

Esta pergunta tem uma resposta aceita, mas apenas para esclarecer, a autenticação secreta compartilhada funciona assim:

  1. O cliente possui uma chave pública, esta pode ser compartilhada com qualquer pessoa, não importa, então você pode embuti-la em javascript. Isso é usado para identificar o usuário no servidor.
  2. O servidor tem uma chave secreta e esse segredo DEVE ser protegido. Portanto, a autenticação de chave compartilhada requer que você possa proteger sua chave secreta. Portanto, um cliente javascript público que se conecta diretamente a outro serviço não é possível porque você precisa de um intermediário de servidor para proteger o segredo.
  3. O servidor assina a solicitação usando algum algoritmo que inclui a chave secreta (a chave secreta é como um sal) e, de preferência, um carimbo de data / hora, em seguida, envia a solicitação ao serviço. O carimbo de data / hora serve para evitar ataques de "repetição". A assinatura de uma solicitação só é válida por cerca de n segundos. Você pode verificar isso no servidor, obtendo o cabeçalho do carimbo de data / hora que deve conter o valor do carimbo de data / hora incluído na assinatura. Se esse carimbo de data / hora tiver expirado, a solicitação falhará.
  4. O serviço obtém a solicitação que contém não apenas a assinatura, mas também todos os campos assinados em texto simples.
  5. O serviço então assina a solicitação da mesma maneira, usando a chave secreta compartilhada e compara as assinaturas.
chris
fonte
Verdadeiro, mas por design sua resposta não expõe a chave de API. No entanto, em algumas APIs a chave da API é publicamente visível, e é sobre isso que se tratava a pergunta: "solicitações para uma operação de serviço de descanso [...] feitas através de javascript (XHR / Ajax)" . (A resposta aceita também está errada sobre isso, eu acho; seu ponto 2 é claro sobre isso, ótimo.)
Arjan
1

Suponho que você queira dizer a chave de sessão, não a chave de API. Esse problema é herdado do protocolo http e conhecido como sequestro de sessão . A "solução alternativa" normal é, como em qualquer site, mudar para https.

Para executar o serviço REST seguro, você deve habilitar https e provavelmente a autenticação do cliente. Mas, afinal, isso está além da ideia do REST. REST nunca fala sobre segurança.

PeterMmm
fonte
8
Na verdade, eu quis dizer a chave. Se bem me lembro, para usar uma API, você está passando a chave e o segredo da API para o serviço restante autenticar, correto? Eu sei que uma vez que ele passa pela rede, ele é criptografado por SSL, mas antes de ser enviado, é perfeitamente visível pelo código do cliente que o usa ...
tjans
1

O que você deseja fazer no lado do servidor é gerar um ID de sessão expirado que é enviado de volta ao cliente no login ou inscrição. O cliente pode então usar esse id de sessão como um segredo compartilhado para assinar solicitações subsequentes.

O id da sessão é passado apenas uma vez e DEVE ser por SSL.

Veja o exemplo aqui

Use um nonce e um carimbo de data / hora ao assinar a solicitação para evitar o sequestro de sessão.

Iain Porter
fonte
1
Mas como pode haver qualquer login quando um terceiro usa sua API? Se o usuário for fazer o login, as coisas são fáceis: basta usar uma sessão? Mas quando outros sites precisam se autenticar em sua API, isso não ajuda. (Além disso, cheira muito a promoção de seu blog.)
Arjan
1

Vou tentar responder à pergunta em seu contexto original. Portanto, a questão é "A chave secreta (API) é segura para ser colocada em JavaScript.

Na minha opinião é muito inseguro, pois vai contra o propósito de autenticação entre os sistemas. Uma vez que a chave será exposta ao usuário, o usuário pode recuperar informações para as quais não está autorizado. Porque em uma autenticação típica de comunicação de repouso é baseada apenas na chave API.

Uma solução, em minha opinião, é que a chamada de JavaScript essencialmente passa a solicitação para um componente interno do servidor que é responsável por fazer uma chamada de descanso. O componente interno do servidor, digamos que um Servlet lerá a chave API de uma fonte segura, como um sistema de arquivos baseado em permissão, inserirá no cabeçalho HTTP e fará a chamada externa restante.

Eu espero que isso ajude.

MG Developer
fonte