Práticas recomendadas do SPA para autenticação e gerenciamento de sessões

308

Ao criar aplicativos no estilo SPA usando estruturas como Angular, Ember, React etc. etc., quais são as melhores práticas para autenticação e gerenciamento de sessões? Posso pensar em algumas maneiras de considerar abordar o problema.

  1. Trate-o de maneira diferente da autenticação com um aplicativo Web regular, assumindo que a API e a UI tenham o mesmo domínio de origem.

    Isso provavelmente envolveria ter um cookie de sessão, armazenamento de sessão no servidor e provavelmente um ponto de extremidade da API da sessão que a interface do usuário da web autenticada pode acessar para obter informações atuais do usuário para ajudar na personalização ou possivelmente até na determinação de funções / habilidades no lado do cliente. O servidor ainda aplicaria regras que protegem o acesso aos dados, é claro, a interface do usuário apenas usaria essas informações para personalizar a experiência.

  2. Trate-o como qualquer cliente de terceiros usando uma API pública e autentique com algum tipo de sistema de token semelhante ao OAuth. Esse mecanismo de token seria usado pela interface do usuário do cliente para autenticar toda e qualquer solicitação feita à API do servidor.

Não sou muito especialista aqui, mas o número 1 parece ser suficiente para a grande maioria dos casos, mas eu realmente gostaria de ouvir opiniões mais experientes.

Chris Nicola
fonte
I perfer Desta forma, stackoverflow.com/a/19820685/454252
allenhwkim

Respostas:

477

Esta questão foi abordada, de uma forma ligeiramente diferente, detalhadamente aqui:

Autenticação RESTful

Mas isso aborda do lado do servidor. Vejamos isso do lado do cliente. Antes de fazer isso, porém, há um prelúdio importante:

O Javascript Crypto é impossível

O artigo de Matasano sobre isso é famoso, mas as lições nele contidas são muito importantes:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

Para resumir:

  • Um ataque man-in-the-middle pode substituir trivialmente seu código criptográfico por <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Um ataque man-in-the-middle é trivial contra uma página que serve qualquer recurso através de uma conexão não SSL.
  • Depois de ter SSL, você estará usando criptografia real de qualquer maneira.

E para adicionar um corolário meu:

  • Um ataque XSS bem-sucedido pode resultar em um invasor executando o código no navegador do seu cliente, mesmo se você estiver usando SSL - mesmo se você tiver todas as hachuras desativadas, a criptografia do navegador ainda poderá falhar se o invasor encontrar uma maneira de executar qualquer código javascript no navegador de outra pessoa.

Isso torna muitos esquemas de autenticação RESTful impossíveis ou tolos se você pretende usar um cliente JavaScript. Vamos olhar!

Autenticação básica HTTP

Em primeiro lugar, o HTTP Basic Auth. O mais simples dos esquemas: basta passar um nome e senha a cada solicitação.

Obviamente, isso exige absolutamente SSL, porque você passa um nome e uma senha codificados em Base64 (reversivelmente) a cada solicitação. Qualquer pessoa que escute na linha poderá extrair nome de usuário e senha trivialmente. A maioria dos argumentos "A autenticação básica é insegura" vem de um local "A autenticação básica sobre HTTP", que é uma péssima idéia.

O navegador fornece suporte básico à autenticação HTTP básica, mas é feio como o pecado e você provavelmente não deve usá-lo no seu aplicativo. A alternativa, porém, é esconder o nome de usuário e a senha em JavaScript.

Esta é a solução mais RESTful. O servidor não requer nenhum conhecimento do estado e autentica todas as interações individuais com o usuário. Alguns entusiastas do REST (principalmente homens de palha) insistem que manter qualquer tipo de estado é uma heresia e se espalhará pela boca se você pensar em outro método de autenticação. Existem benefícios teóricos para esse tipo de conformidade com os padrões - com o Apache pronto para uso - você pode armazenar seus objetos como arquivos em pastas protegidas por arquivos .htaccess, se desejar.

O problema ? Você está armazenando em cache no lado do cliente um nome de usuário e senha. Isso dá ao evil.ru uma solução melhor - mesmo as vulnerabilidades mais básicas do XSS podem resultar no cliente transferir seu nome de usuário e senha para um servidor inválido. Você pode tentar aliviar esse risco usando hash e salgando a senha, mas lembre-se: o JavaScript Crypto é impossível . Você pode aliviar esse risco deixando-o para o suporte de autenticação básica do navegador, mas feio como o pecado, como mencionado anteriormente.

Autenticação de resumo HTTP

A autenticação Digest é possível com o jQuery?

Uma autenticação mais "segura", este é um desafio de hash de solicitação / resposta. Exceto que o JavaScript Crypto é impossível , portanto, ele funciona apenas com SSL e você ainda precisa armazenar em cache o nome de usuário e a senha no lado do cliente, tornando-o mais complicado que o HTTP Basic Auth, mas não mais seguro .

Autenticação de consulta com parâmetros de assinatura adicionais.

Outra autenticação mais "segura", na qual você criptografa seus parâmetros com dados não temporários e de tempo (para proteger contra ataques de repetição e tempo) e envia o arquivo. Um dos melhores exemplos disso é o protocolo OAuth 1.0, que é, até onde eu sei, uma maneira bastante difícil de implementar a autenticação em um servidor REST.

http://tools.ietf.org/html/rfc5849

Ah, mas não há clientes OAuth 1.0 para JavaScript. Por quê?

JavaScript Crypto é impossível , lembre-se. O JavaScript não pode participar do OAuth 1.0 sem SSL, e você ainda precisa armazenar o nome de usuário e a senha do cliente localmente - o que o coloca na mesma categoria que o Digest Auth - é mais complicado que o HTTP Basic Auth, mas não é mais seguro .

Símbolo

O usuário envia um nome de usuário e senha e, em troca, recebe um token que pode ser usado para autenticar solicitações.

Isso é marginalmente mais seguro que o HTTP Basic Auth, porque assim que a transação de nome de usuário / senha é concluída, você pode descartar os dados confidenciais. Também é menos RESTful, pois os tokens constituem "estado" e tornam a implementação do servidor mais complicada.

Ainda SSL

O problema, porém, é que você ainda precisa enviar esse nome de usuário e senha iniciais para obter um token. Informações confidenciais ainda tocam no seu JavaScript comprometível.

Para proteger as credenciais do usuário, você ainda precisa manter os invasores fora do seu JavaScript e ainda precisa enviar um nome de usuário e senha pelo telefone. SSL obrigatório.

Expiração de token

É comum impor políticas de token como "ei, quando esse token já existe há muito tempo, descarte-o e faça o usuário se autenticar novamente". ou "Tenho certeza de que o único endereço IP permitido para usar esse token é XXX.XXX.XXX.XXX". Muitas dessas políticas são boas idéias.

Firesheeping

No entanto, o uso de um token Without SSL ainda é vulnerável a um ataque chamado 'sidejacking': http://codebutler.github.io/firesheep/

O invasor não obtém as credenciais do usuário, mas ainda pode fingir ser seu usuário, o que pode ser muito ruim.

tl; dr: O envio de tokens não criptografados por cabo significa que os invasores podem facilmente pegá-los e fingir ser seu usuário. O FireSheep é um programa que facilita muito isso.

Uma zona separada e mais segura

Quanto maior o aplicativo em execução, mais difícil é garantir absolutamente que eles não possam injetar algum código que mude a maneira como você processa dados confidenciais. Você confia absolutamente no seu CDN? Seus anunciantes? Sua própria base de código?

Comum para detalhes de cartão de crédito e menos comum para nome de usuário e senha - alguns implementadores mantêm 'entrada de dados confidenciais' em uma página separada do restante do aplicativo, uma página que pode ser rigidamente controlada e bloqueada da melhor forma possível, de preferência uma que é difícil phishing usuários com.

Cookie (apenas significa Token)

É possível (e comum) colocar o token de autenticação em um cookie. Isso não altera nenhuma das propriedades de auth com o token, é mais uma coisa de conveniência. Todos os argumentos anteriores ainda se aplicam.

Sessão (ainda significa apenas token)

A autenticação de sessão é apenas autenticação de token, mas com algumas diferenças que fazem parecer uma coisa um pouco diferente:

  • Os usuários começam com um token não autenticado.
  • O back-end mantém um objeto 'state' vinculado ao token de um usuário.
  • O token é fornecido em um cookie.
  • O ambiente do aplicativo abstrai os detalhes de você.

Além disso, porém, não é diferente do Token Auth, na verdade.

Isso se afasta ainda mais de uma implementação RESTful - com objetos de estado, você está indo cada vez mais longe do caminho da RPC simples em um servidor com estado.

OAuth 2.0

O OAuth 2.0 analisa o problema de "Como o Software A concede ao Software B acesso aos dados do Usuário X sem que o Software B tenha acesso às credenciais de login do Usuário X".

A implementação é apenas uma maneira padrão de um usuário obter um token e, em seguida, de um serviço de terceiros "sim, esse usuário e esse token correspondem, e você pode obter alguns dos dados deles agora".

Fundamentalmente, porém, o OAuth 2.0 é apenas um protocolo de token. Ele exibe as mesmas propriedades que outros protocolos de token - você ainda precisa de SSL para proteger esses tokens - apenas altera a forma como esses tokens são gerados.

Há duas maneiras pelas quais o OAuth 2.0 pode ajudá-lo:

  • Fornecendo autenticação / informações a terceiros
  • Obtendo autenticação / informações de outras pessoas

Mas quando se trata disso, você está apenas ... usando tokens.

Voltar à sua pergunta

Portanto, a pergunta que você está perguntando é "devo armazenar meu token em um cookie e fazer com que o gerenciamento automático de sessões do meu ambiente cuide dos detalhes ou devo armazenar meu token em Javascript e lidar com esses detalhes sozinho?"

E a resposta é: faça o que te faz feliz .

O problema do gerenciamento automático de sessões é que há muita mágica acontecendo nos bastidores para você. Muitas vezes, é melhor controlar você mesmo esses detalhes.

Tenho 21 anos, então o SSL é sim

A outra resposta é: use https para tudo ou bandidos roubam as senhas e os tokens dos usuários.

Curtis Lassam
fonte
3
Ótima resposta. Aprecio a equivalência entre sistemas de autenticação de token e autenticação básica de cookie (que geralmente é incorporada à estrutura da web). Era o que eu estava procurando. Agradeço que você também tenha coberto tantos problemas em potencial. Felicidades!
Chris Nicola
11
Sei que já faz um tempo, mas estou me perguntando se isso deve ser expandido para incluir o JWT. auth0.com/blog/2014/01/07/…
Chris Nicola
14
O It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." REST do token (1) requer que o servidor seja sem estado. Um token armazenado no lado do cliente não representa o estado de maneira significativa para o servidor. (2) Código marginalmente mais complicado do lado do servidor não tem nada a ver com RESTfulness.
soupdog
10
lol_nope_send_it_to_me_insteadEu amei o nome dessa função: D
Leo
6
Uma coisa que você parece ignorar: os cookies são XSS seguros quando marcados como httpOnly e podem ser bloqueados ainda mais com o secure e o mesmo site. E o manuseio de cookies existe há muito mais tempo === mais batalha endurecida. Contar com JS e armazenamento local para lidar com a segurança de tokens é um jogo tolo.
Martijn Pieters
57

Você pode aumentar a segurança no processo de autenticação usando JWT (JSON Web Tokens) e SSL / HTTPS.

A identificação básica de autenticação / sessão pode ser roubada via:

  • Ataque MITM (Man-In-The-Middle) - sem SSL / HTTPS
  • Um invasor que obtém acesso ao computador de um usuário
  • XSS

Ao usar o JWT, você criptografa os detalhes de autenticação do usuário e armazena no cliente, enviando-o juntamente com todas as solicitações para a API, onde o servidor / API valida o token. Ele não pode ser descriptografado / ler sem a chave privada (que o / lojas API secretamente servidor) Leia atualização .

O novo fluxo (mais seguro) seria:

Conecte-se

  • O usuário efetua login e envia credenciais de login para a API (sobre SSL / HTTPS)
  • API recebe credenciais de login
  • Se válido:
    • Registrar uma nova sessão no banco de dados Ler atualização
    • Criptografar ID do usuário, ID da sessão, endereço IP, carimbo de data e hora, etc. em um JWT com uma chave privada.
  • A API envia o token JWT de volta ao cliente (sobre SSL / HTTPS)
  • O cliente recebe o token JWT e armazena no localStorage / cookie

Toda solicitação para API

  • O usuário envia uma solicitação HTTP para a API (sobre SSL / HTTPS) com o token JWT armazenado no cabeçalho HTTP
  • A API lê o cabeçalho HTTP e descriptografa o token JWT com sua chave privada
  • A API valida o token JWT, corresponde o endereço IP da solicitação HTTP ao endereço no token JWT e verifica se a sessão expirou
  • Se válido:
    • Retornar resposta com o conteúdo solicitado
  • Se inválido:
    • Lançar exceção (403/401)
    • Intrusão de sinalizador no sistema
    • Envie um email de aviso para o usuário.

Atualizado 30.07.15:

A carga / reivindicações do JWT pode realmente ser lida sem a chave privada (secreta) e não é seguro armazená-la no localStorage. Sinto muito por essas declarações falsas. No entanto, eles parecem estar trabalhando em um padrão JWE (JSON Web Encryption) .

Eu implementei isso armazenando declarações (ID do usuário, exp) em um JWT, assinei-a com uma chave privada (segredo) que a API / backend apenas conhece e a armazene como um cookie HttpOnly seguro no cliente. Dessa forma, ele não pode ser lido via XSS e não pode ser manipulado; caso contrário, o JWT falhará na verificação da assinatura. Além disso, usando um cookie HttpOnly seguro , você garante que o cookie seja enviado apenas por solicitações HTTP (não acessível ao script) e enviado apenas por conexão segura (HTTPS).

Atualizado 17.07.16:

Os JWTs são por natureza apátridas. Isso significa que eles se invalidam / expiram. Ao adicionar o SessionID nas declarações do token, você está tornando-o com estado, porque sua validade agora não depende apenas da verificação da assinatura e da data de validade, mas também do estado da sessão no servidor. No entanto, a vantagem é que você pode invalidar tokens / sessões facilmente, o que não era possível antes com JWTs sem estado.

Gaui
fonte
1
No final, um JWT ainda é 'apenas um token' do ponto de vista da segurança, eu acho. O servidor ainda pode associar o ID do usuário, endereço IP, carimbo de data e hora, etc. a um token de sessão opaco e não seria mais ou menos seguro que um JWT. No entanto, a natureza sem estado do JWT facilita a implementação.
James
1
@James, o JWT tem a vantagem de ser verificável e capaz de transmitir detalhes importantes. Isso é bastante útil para vários cenários de API, como onde a autenticação entre domínios é necessária. Algo para o qual uma sessão não será tão boa. Também é uma especificação definida (ou pelo menos em andamento), útil para implementações. Isso não quer dizer que seja melhor do que qualquer outra boa implementação de token, mas é bem definida e conveniente.
Chris Nicola
1
@ Chris Sim, eu concordo com todos os seus pontos. No entanto, o fluxo descrito na resposta acima não é inerentemente um fluxo mais seguro, conforme reivindicado devido ao uso de um JWT. Além disso, o JWT não é revogável no esquema descrito acima, a menos que você associe um identificador ao JWT e ao estado de armazenamento no servidor. Caso contrário, você precisará obter regularmente um novo JWT solicitando nome de usuário / senha (baixa experiência do usuário) ou emitir um JWT com um tempo de expiração muito longo (ruim se o token for roubado).
James
1
Minha resposta não está 100% correta, porque o JWT pode realmente ser descriptografado / lido sem a chave privada (secreta) e não é seguro armazená-lo no localStorage. Eu implementei isso armazenando declarações (ID do usuário, exp) em um JWT, assinei-a com uma chave privada (segredo) que a API / back-end apenas conhece e a armazene como um cookie HttpOnly no cliente. Dessa forma, não pode ser lido pelo XSS. Mas você precisa usar HTTPS porque o token pode ser roubado com um ataque MITM. Vou atualizar minha resposta para refletir sobre isso.
Gaui 30/07/2015
1
@vsenko O cookie é enviado com cada solicitação do cliente. Você não acessa o cookie do JS, ele é vinculado a cada solicitação HTTP do cliente para a API.
Gaui
7

Eu iria para o segundo, o sistema de token.

Você sabia sobre ember-auth ou ember-simple-auth ? Ambos usam o sistema baseado em token, como estados ember-simple-auth:

Uma biblioteca leve e discreta para implementar autenticação baseada em token nos aplicativos Ember.js. http://ember-simple-auth.simplabs.com

Eles possuem gerenciamento de sessões e também são fáceis de conectar a projetos existentes.

Há também uma versão de exemplo do Ember App Kit do ember-simple-auth: Exemplo de trabalho do ember-app-kit usando o ember-simple-auth para autenticação OAuth2.

DelphiLynx
fonte