autenticação RESTful do passport.js


155

Como alguém lida com autenticação (local e Facebook, por exemplo) usando passport.js, por meio de uma API RESTful em vez de por uma interface da Web?

Preocupações específicas estão lidando com a passagem de dados de retornos de chamada para uma resposta RESTful (JSON) vs usando um res.send típico ({data: req.data}), configurando um terminal de início / login que redireciona para o Facebook (/ login não pode ser acessado via AJAX, porque não é uma resposta JSON - é um redirecionamento para o Facebook com um retorno de chamada).

Encontrei https://github.com/halrobertson/test-restify-passport-facebook , mas estou tendo problemas para entendê-lo.

Além disso, como o passport.js armazena as credenciais de autenticação? O servidor (ou é serviço?) É suportado pelo MongoDB, e eu espero que as credenciais (login e hash salgado de pw) sejam armazenadas lá, mas não sei se o passport.js tem esse tipo de recurso.


Desde que você é novo para Node, começar fácil e confira a aplicação exemplo para passport-facebook. Depois que você começar a trabalhar, o próximo passo é começar a entender como o Passport funciona e como ele armazena credenciais. Conectá-lo ao Restify ( veja aqui uma versão atualizada da mencionada) seria uma das últimas etapas (ou você pode implementar a interface REST no Express).
robertklep

Respostas:


312

Há muitas perguntas feitas aqui, e parece que, embora as perguntas sejam feitas no contexto do Node e do passport.js, as perguntas reais são mais sobre fluxo de trabalho do que como fazer isso com uma tecnologia específica.

Vamos usar a configuração de exemplo do @Keith, modificada um pouco para aumentar a segurança:

  • O servidor da Web em https://example.comserve um aplicativo cliente Javascript de página única
  • O serviço da web RESTful em https://example.com/apifornece suporte ao servidor para aplicativo rich client
  • Servidor implementado no Node e no passport.js.
  • O servidor possui um banco de dados (qualquer tipo) com uma tabela "users".
  • Nome de usuário / senha e Facebook Connect são oferecidos como opções de autenticação
  • O Rich Client faz solicitações REST em https://example.com/api
  • Pode haver outros clientes (aplicativos de telefone, por exemplo) que usam o serviço da Web em, https://example.com/apimas não conhecem o servidor da Web em https://example.com.

Observe que estou usando HTTP seguro. Na minha opinião, isso é obrigatório para qualquer serviço disponível ao ar livre, pois informações confidenciais como senhas e tokens de autorização estão passando entre o cliente e o servidor.

Autenticação de nome de usuário / senha

Vamos ver como a autenticação antiga simples funciona primeiro.

  • O usuário se conecta a https://example.com
  • O servidor serve um aplicativo Javascript avançado que renderiza a página inicial. Em algum lugar da página existe um formulário de login.
  • Muitas das seções deste aplicativo de página única não foram preenchidas com dados devido ao fato de o usuário não estar conectado. Todas essas seções têm um ouvinte de evento em um evento de "login". Tudo isso é coisa do lado do cliente, o servidor não conhece esses eventos.
  • O usuário digita seu login e senha e pressiona o botão Enviar, que aciona um manipulador Javascript para registrar o nome de usuário e a senha nas variáveis ​​do lado do cliente. Em seguida, esse manipulador aciona o evento "login". Novamente, isso é tudo ação do lado do cliente, as credenciais ainda não foram enviadas para o servidor .
  • Os ouvintes do evento "login" são chamados. Agora, cada um deles precisa enviar uma ou mais solicitações à API RESTful https://example.com/apipara obter os dados específicos do usuário a serem renderizados na página. Todas as solicitações enviadas ao serviço da Web incluirão o nome de usuário e a senha, possivelmente na forma de autenticação HTTP Basic , pois o serviço RESTful não tem permissão para manter o estado do cliente de uma solicitação para a próxima. Como o serviço da Web está em HTTP seguro, a senha é criptografada com segurança durante o trânsito.
  • O serviço da Web https://example.com/apirecebe um monte de solicitações individuais, cada uma com informações de autenticação. O nome de usuário e a senha em cada solicitação são verificados no banco de dados do usuário e, se encontrados corretamente, a função solicitada é executada e os dados são retornados ao cliente no formato JSON. Se o nome de usuário e a senha não corresponderem, um erro será enviado ao cliente na forma de um código de erro HTTP 401.
  • Em vez de forçar os clientes a enviar nome de usuário e senha com todas as solicitações, você pode ter uma função "get_access_token" em seu serviço RESTful que pega o nome de usuário e a senha e responde com um token, que é algum tipo de hash criptográfico exclusivo e com alguma validade data associada a ele. Esses tokens são armazenados no banco de dados com cada usuário. Em seguida, o cliente envia o token de acesso em solicitações subsequentes. O token de acesso será validado no banco de dados, em vez do nome de usuário e senha.
  • Aplicativos clientes que não são navegadores, como aplicativos de telefone, fazem o mesmo que acima, eles solicitam que o usuário insira suas credenciais e os envie (ou um token de acesso gerado a partir deles) com todas as solicitações ao serviço da web.

O ponto importante deste exemplo é que os serviços da Web RESTful requerem autenticação com cada solicitação .

Uma camada adicional de segurança nesse cenário adicionaria autorização de aplicativo cliente, além da autenticação do usuário. Por exemplo, se você tiver os aplicativos cliente da Web, iOS e Android usando o serviço da Web, poderá desejar que o servidor saiba qual dos três é o cliente de uma determinada solicitação, independentemente de quem é o usuário autenticado. Isso pode permitir que seu serviço da web restrinja determinadas funções a clientes específicos. Para isso, você pode usar chaves e segredos da API. Consulte esta resposta para obter algumas idéias sobre isso.

Autenticação do Facebook

O fluxo de trabalho acima não funciona para a conexão com o Facebook porque o login via Facebook possui terceiros, o próprio Facebook. O procedimento de login exige que o usuário seja redirecionado para o site do Facebook, onde as credenciais são inseridas fora do nosso controle.

Então, vamos ver como as coisas mudam :.

  • O usuário se conecta a https://example.com
  • O servidor serve um aplicativo Javascript avançado que renderiza a página inicial. Em algum lugar da página, existe um formulário de login que inclui o botão "Login com o Facebook".
  • O usuário clica no botão "Login with Facebook", que é apenas um link que redireciona para (por exemplo) https://example.com/auth/facebook.
  • A https://example.com/auth/facebookrota é tratada pelo passport.js (consulte a documentação )
  • Tudo o que o usuário vê é que a página muda e agora eles estão em uma página hospedada no Facebook, onde precisam fazer login e autorizar nosso aplicativo da web. Isso está completamente fora do nosso controle.
  • O usuário se conecta ao Facebook e dá permissão para a nossa aplicação, de modo Facebook agora redireciona para a URL de retorno que nós configurado na configuração passport.js, que seguindo o exemplo na documentação éhttps://example.com/auth/facebook/callback
  • O manipulador passport.js da https://example.com/auth/facebook/callbackrota invocará a função de retorno de chamada que recebe o token de acesso do Facebook e algumas informações do usuário do Facebook, incluindo o endereço de e-mail do usuário.
  • Com o email, podemos localizar o usuário em nosso banco de dados e armazenar o token de acesso do Facebook.
  • A última coisa que você faz no retorno de chamada do Facebook é redirecionar de volta para o aplicativo rich client, mas desta vez precisamos passar o nome de usuário e o token de acesso ao cliente para que ele possa usá-los. Isso pode ser feito de várias maneiras. Por exemplo, variáveis ​​Javascript podem ser adicionadas à página por meio de um mecanismo de modelo do servidor, ou um cookie pode ser retornado com essas informações. (obrigado a @RyanKimber por apontar os problemas de segurança ao passar esses dados no URL, como sugeri inicialmente).
  • Portanto, agora iniciamos o aplicativo de página única mais uma vez, mas o cliente tem o nome de usuário e o token de acesso.
  • O aplicativo cliente pode acionar o evento "login" imediatamente e permitir que as diferentes partes do aplicativo solicitem as informações necessárias no serviço da web.
  • Todas as solicitações enviadas para https://example.com/apiincluirão o token de acesso do Facebook para autenticação ou o próprio token de acesso do aplicativo gerado a partir do token do Facebook por meio de uma função "get_access_token" na API REST.
  • Os aplicativos que não são de navegador têm um pouco mais de dificuldade aqui, porque o OAuth requer um navegador da Web para efetuar login. Para fazer login em um aplicativo de telefone ou desktop, você precisará iniciar um navegador para redirecionar para o Facebook e, pior ainda, É necessário que o navegador transmita o token de acesso do Facebook de volta ao aplicativo por meio de algum mecanismo.

Espero que isso responda à maioria das perguntas. É claro que você pode substituir o Facebook pelo Twitter, Google ou qualquer outro serviço de autenticação baseado em OAuth.

Eu estaria interessado em saber se alguém tem uma maneira mais simples de lidar com isso.


5
Obrigado pela sua resposta detalhada. Apenas uma pergunta: você diz isso Every single request they send to the web service will include the username and passworde, no entanto, diz you can have a "get_access_token" function in your RESTful service. Parece contraditório dizer que o REST precisa ser sem estado, mas armazenar os tokens de acesso do lado do servidor é OK, pois esse ato de armazenar tokens de acesso significa que o servidor agora está com estado. Gostaria de receber qualquer esclarecimento ou justificativa sobre isso. Obrigado! :)
ryanrhee

6
Quando penso no requisito sem estado, penso no estado de uma sessão específica com um cliente. Não vejo o armazenamento de dados associado a um usuário em um banco de dados, como uma senha ou um token de acesso como "estado", pelo menos não "estado da sessão". Seu servidor obviamente precisa saber quem são os usuários e suas senhas, mas esses são dados de aplicativos que não estão associados a uma sessão. Dito isto, muitas pessoas usam cookies em serviços supostamente RESTful; portanto, quão rigoroso você deseja aderir à especificação REST depende realmente de cada implementador.
Miguel

1
@Dexter: no caso tradicional de login, um usuário digita o nome de usuário e a senha em um formulário e, quando ele pressiona o botão Enviar, essas informações são postadas em um servidor da web. Nesse caso, isso não acontece, o usuário preenche o formulário e quando ele pressiona Enviar um manipulador Javascript (um evento onClick no botão enviar) captura os dados e os mantém no contexto do cliente. Eu não tenho um exemplo pronto para mostrar, mas observe uma segunda parte deste tutorial no meu blog, onde mostrarei como isso é feito: blog.miguelgrinberg.com/post/…
Miguel

2
Esta é uma redação muito bem pensada, mas contém uma importante supervisão ou erro. Ao manipular o logon do Facebook (ou Github, twitter, etc), seria muito preferível passar o token de volta para o cliente em um cookie e excluir o cookie no lado do cliente assim que for descoberto. Passar o token como parte da cadeia de URLs adicionará esse URL ao histórico do navegador e (se as coisas forem tratadas incorretamente) poderá levar o URL a ser solicitado pelo navegador. Qualquer um torna o token visível. Qualquer pessoa que copie o URL poderá falsificar esse usuário no seu aplicativo.
Ryan Kimber

1
@ Nathan: a autenticação básica por https é segura, então sim, esse é um mecanismo aceitável.
Miguel

11

Agradeço imensamente a explicação de @ Miguel com o fluxo completo em cada um dos casos, mas gostaria de adicionar um pouco à parte de Autenticação do Facebook.

O Facebook fornece um SDK Javascript que você pode usar para obter o token de acesso diretamente no cliente, que é passado ao servidor e usado para extrair ainda mais todas as informações do usuário do Facebook. Portanto, você não precisa de redirecionamentos basicamente.

Além disso, você também pode usar o mesmo ponto de extremidade da API para aplicativos móveis. Basta usar o Android / iOS SDK para Facebook, obter o access_token do Facebook no final do cliente e passá-lo para o servidor.

Em relação à natureza sem estado, conforme explicado, quando get_access_token é usado para gerar um token e passado ao cliente, esse token também é armazenado no servidor. Portanto, é tão bom quanto um token de sessão e acredito que isso o torna estável.

Apenas meus 2 centavos ..


1
Isso é muito importante, pois dessa maneira você pode executar uma autenticação somente ajax de uma página usando o facebook. O token é IGUALMENTE seguro como autenticação de sessão, a única diferença é que um token pode ser passado para outro domínio e a sessão é usada apenas em um domínio específico. Ao usar expressjs e passaporte, você pode criar uma API que salva um estado e usar a autenticação de sessão.
jperelli

A API javascript é ótima se você deseja autenticar o usuário para executar ações no Facebook, mas inútil por si só se deseja validar o usuário no servidor / banco de dados, tanto quanto eu sei.
James Westgate

4
Você também pode usar o método descrito por Miguel acima, mas depois emitir seu próprio token JWT como um cookie ao redirecionar o cliente no retorno de chamada de autenticação. Isso permite o melhor dos dois mundos - seu aplicativo de página única pode se preocupar apenas com um tipo de autenticação (JWT), mantendo o mesmo nível de segurança e fornecendo a flexibilidade para oferecer suporte a qualquer um dos logins sociais sem precisar usar o APIs JavaScript específicas para cada rede social (Facebook, Twitter, LinkedIn, Google, etc.). Também permite manter o suporte no estilo AJAX para nome de usuário / senha e acesso REST.
Ryan Kimber

No momento, o SDK Javascript do Facebook não funciona no Chrome iOS. Talvez um problema para alguns.
demisx

@RyanKimber, você pode escrever um pequeno repositório Git ou similar, onde isso é feito como um exemplo, estou totalmente preso
Simon Dragsbæk

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.