Feathers: Adicionar suporte para tokens de atualização

Criado em 22 dez. 2015  ·  64Comentários  ·  Fonte: feathersjs/feathers

Atualmente, permitimos obter um novo token postando um token de autenticação válido em <loginEndpoint>/refresh . Os tokens de atualização têm um fluxo de trabalho ligeiramente diferente, conforme explicado aqui:
https://auth0.com/learn/refresh-tokens

Authentication Feature

Comentários muito úteis

Este recurso é OBRIGATÓRIO quando se trata de aplicativos React Native. O usuário efetua login no início e quando abre o aplicativo após várias semanas, ele espera ainda estar conectado.

Todos 64 comentários

: +1: @corymsmith e eu estávamos conversando sobre isso. Esperando ajudar a chutar um pouco disso na linha de chegada durante as "férias".

Temos suporte para isso no master, mas também temos suporte para isso no branch decoupling . Para atualizar um token, você tem 2 opções:

  1. Você pode reautenticar usando e-mail / senha, twitter, etc.
  2. Você pode passar um token válido para GET /auth/token/refresh

Temos um processo de renovação de tokens em vigor, mas não temos suporte para tokens de atualização totalmente completos, conforme descrito no link Auth0 que postei acima. Um token de atualização real funciona de maneira semelhante a um código / senha de autenticação do GitHub, mas só pode ser usado para obter um novo token JWT. Portanto, mesmo que seu token JWT expire, se você tiver um token de atualização, poderá usá-lo para fazer login novamente. Eles são mantidos no banco de dados com userId intacto e podem ser revogados a qualquer momento. Pelo menos, é isso que estou recolhendo do artigo Auth0.

Ah você está certo @marshallswain. Acho que deveria ter clicado no link: wink:

Acho que para o primeiro corte vamos deixar isso fora do marco 1.0. É fácil o suficiente para as pessoas se autenticarem novamente.

Eu meio que voto que tornemos isso feathers-authentication 2.0.

Grandes mentes.

Ainda estou muito confuso, pois não está claro como funciona exatamente o fluxo de trabalho de autenticação.

O que estou fazendo agora é.
1.) O cliente envia o nome de usuário e a senha

 curl -X POST https://xxx/auth/local   -H "Content-Type: application/json"   -d '{ "email":"xxx", "password":"yyy"}'

Isso retorna o token JWT.

{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjYyODUsImV4cCI6MTQ3MDQxMjY4NSwiaXNzIjoiZmVhdGhlcnMifQ.OVvQbnxfoDGxPFm3Y6tBhRae2Qa6_mDq-PVIo8RcC8Y"}

2.) Em seguida, coloquei este token no cabeçalho http Authorizatin para acessar a API.

curl -X GET https://xxx/users  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY'

O que eu não entendo é como atualizar esse token.
O que tentei foi enviar este token para xxx / auth / token / refresh
O que eu tenho é apenas outro token muito longo. Em seguida, tentei usar o token antigo e o novo para acessar a API. ambos funcionam ... (o antigo não deveria ser desativado?)

curl -X GET https://xxx/auth/token/refresh  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY'
{"query":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY"},"provider":"rest","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJxdWVyeSI6eyJ0b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpmYVdRaU9pSTFOemhoTmpVeU4yUmtNVFppTWpJd01EUmhZMlpqTm1FaUxDSnBZWFFpT2pFME56QXpNalUxTnpZc0ltVjRjQ0k2TVRRM01EUXhNVGszTml3aWFYTnpJam9pWm1WaGRHaGxjbk1pZlEuX0NIZHgzUnBFdUkxODl0OTBtWHEtSU1QWFJOdW9WaDduQndZMU9ON3hDWSJ9LCJwcm92aWRlciI6InJlc3QiLCJ0b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpmYVdRaU9pSTFOemhoTmpVeU4yUmtNVFppTWpJd01EUmhZMlpqTm1FaUxDSnBZWFFpT2pFME56QXpNalUxTnpZc0ltVjRjQ0k2TVRRM01EUXhNVGszTml3aWFYTnpJam9pWm1WaGRHaGxjbk1pZlEuX0NIZHgzUnBFdUkxODl0OTBtWHEtSU1QWFJOdW9WaDduQndZMU9ON3hDWSIsImRhdGEiOnsiX2lkIjoiNTc4YTY1MjdkZDE2YjIyMDA0YWNmYzZhIiwiaWF0IjoxNDcwMzI1NTc2LCJleHAiOjE0NzA0MTE5NzYsImlzcyI6ImZlYXRoZXJzIiwidG9rZW4iOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpJVXpJMU5pSjkuZXlKZmFXUWlPaUkxTnpoaE5qVXlOMlJrTVRaaU1qSXdNRFJoWTJaak5tRWlMQ0pwWVhRaU9qRTBOekF6TWpVMU56WXNJbVY0Y0NJNk1UUTNNRFF4TVRrM05pd2lhWE56SWpvaVptVmhkR2hsY25NaWZRLl9DSGR4M1JwRXVJMTg5dDkwbVhxLUlNUFhSTnVvVmg3bkJ3WTFPTjd4Q1kifSwiaWF0IjoxNDcwMzI2NDQyLCJleHAiOjE0NzA0MTI4NDIsImlzcyI6ImZlYXRoZXJzIn0.TqUv3051TTGbX4cPfkN-6pOOB5SN9nH-E7TU1HHSsb8","data":{"_id":"578a6527dd16b22004acfc6a","iat":1470325576,"exp":1470411976,"iss":"feathers","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY"}}

Ainda mais estranho é que tentei usar esse novo token e enviar para / auth / token / refresh novamente.
Eu tenho um token ainda mais longo do que este.

Não tenho certeza do que fiz de errado ou mal entendi aqui. Por favor sugira.

@parnurzeal , não temos suporte para token de atualização ainda. É por isso que este é um recurso proposto.

A maneira de obter um novo token é fazer um POST em /auth/token com seu JWT válido existente ou fazer login usando outro mecanismo de autenticação. Parece que você está fazendo tudo certo.

Certo, mas por favor, olhe de perto como me saí e o resultado que obtive.

Vamos usar um exemplo fácil.
Quando eu solicito um novo token usando abcdefghijklmno (apenas token aleatório sem sentido).
A resposta de volta é apenas uma versão mais longa do token anterior -> abcdefghijklmnopqrstuvwxyz
Se eu tentar fazer novamente usando abcdefghijklmnopqrstuvwxyz , vou obter uma versão mais longa ->
abcdefghijklmnopqrstuvwxyz1234567890 e o loop continua (solicitando mais, você obtém uma versão mais longa do anterior).

Além disso, todos os três tokens acima podem ser usados ​​ao mesmo tempo.
O token anterior não deveria expirar após solicitarmos um novo token?

@parnurzeal o que estou dizendo é não faça o que você fez porque esse recurso não foi realmente implementado. Com base na implementação (até agora), o fato de que o token continua crescendo toda vez que você atinge /auth/token/refresh é porque estamos apenas colocando os dados de volta no token. Não é assim que se pretende que funcione e não tivemos tempo de terminá-lo e por que não está documentado. Você não deve usá-lo.

O token anterior não deveria expirar após solicitarmos um novo token?

Essa é a natureza do JWT. Eles expiram por conta própria de seu TTL. Se você deseja evitar que tokens antigos que ainda não expiraram, sejam usados, você precisa manter uma lista negra. Atualmente, isso depende de você e temos um problema em aberto (# 133) em torno disso, mas provavelmente não chegaremos a isso em breve (se é que o faremos).

Olá, eu olhei em / auth / refresh / token e descobri algo assim:

...
function pick (o, ...props) {
  return Object.assign({}, ...props.map(prop => ({[prop]: o[prop]})));
}

// Provider specific config
const defaults = {
  payload: ['id', 'role'],
  passwordField: 'password',
  issuer: 'feathers',
  algorithm: 'HS256',
  expiresIn: '1d', // 1 day
};
...
// GET /auth/token/refresh
  get (id, params) {
    if (id !== 'refresh') {
      return Promise.reject(new errors.NotFound());
    }

    const options = this.options;

    // Add payload fields
    const data = pick(params.payload, options.payload);

    return new Promise(resolve => {
      jwt.sign(data, config.get('auth').token.secret, options, token => {
        return resolve({token: token});
      });
    });

  }

É muito ingênuo como implementação? Se não, eu poderia tentar polir, adicionar alguns testes e criar um PR.

@aboutlo obrigado pelo esforço! É melhor esperar até que a v0.8 seja lançada (ela já está em alpha há um tempo), pois várias mudanças aconteceram e essa rota pode estar indo embora esta semana.
Estou cortando uma versão beta hoje e atualmente encerrando o guia de migração. Portanto, não demorará muito e a v0.8 aborda muitos dos problemas atuais do auth.

Pensamos muito na atualização dos tokens, então, assim que o 0.8 for lançado (esta semana), adoraria abordar esta questão para discutir. Provavelmente irei apresentar nossos pensamentos preliminares no final desta semana.

justo @ekryski , vou esperar pelo 0,8 :)

Este recurso é OBRIGATÓRIO quando se trata de aplicativos React Native. O usuário efetua login no início e quando abre o aplicativo após várias semanas, ele espera ainda estar conectado.

@deiucanta, a boa notícia é que mantivemos esse recurso em mente enquanto criamos o [email protected]. Eu não acho que vai demorar muito para que possamos colocá-lo em prática e documentá-lo.

isso é uma boa notícia! 👍 ansioso por isso

@marshallswain Ansioso por uma atualização sobre este recurso. Por favor, deixe-me saber quando podemos esperar isso. Ou já foi lançado? Desde já, obrigado.

@deiucanta Enquanto isso, até que esse recurso seja lançado, você pode usar tokens de vida mais longa. Depois de lançado, você pode alternar seu segredo de autenticação para um novo valor para eliminar todas as sessões existentes e colocar todos os seus usuários nas sessões mais curtas e renovadoras.

@atulrpandey não foi lançado oficialmente, mas também não é difícil de implementar. Você simplesmente adiciona um gancho para gerar um novo token de atualização e armazená-lo no objeto de usuário no banco de dados e, uma vez que seja usado ou expirado, você o remove do usuário.

@petermikitsh outra coisa que você pode fazer (se estiver no celular) é armazenar clientId e clientSecret segurança no cliente e, se o JWT accessToken expirar, você apenas se autentica novamente com eles.

@ekryski, você pode fornecer um exemplo de qualquer uma dessas estratégias? isso será muito útil.
ou demorará muito para que o suporte oficial seja lançado? isso realmente ajudará na autenticação móvel!

Sobre o tópico de tokens de atualização:

Os tokens de atualização contêm as informações necessárias para obter um novo token de acesso. Em outras palavras, sempre que um token de acesso é necessário para acessar um recurso específico, um cliente pode usar um token de atualização para obter um novo token de acesso emitido pelo servidor de autenticação. Casos de uso comuns incluem obter novos tokens de acesso após a expiração dos antigos ou obter acesso a um novo recurso pela primeira vez. Os tokens de atualização também podem expirar, mas duram bastante. Os tokens de atualização geralmente estão sujeitos a requisitos de armazenamento estritos para garantir que não vazem. Eles também podem ser colocados na https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

Então eu mesmo entrei no React Native antes do que pensava. Estou pensando em possivelmente contribuir com isso, mas gostaria de ter certeza de que entendi completamente a mecânica para ter certeza de que é a implementação correta. Como os tokens de atualização não são sem estado, haverá algumas restrições de uso (por exemplo, os desenvolvedores precisarão fornecer um adaptador de armazenamento).

Você pode usar um JWT válido para obter um token de atualização? Ou os tokens de atualização são retornados automaticamente na resposta de autenticação (por exemplo, além de accessToken )? Parece que Auth0 os está incluindo nas respostas de autenticação ( accessToken e refreshToken ) em seu exemplo em https://auth0.com/learn/refresh-tokens/.

@petermikitsh Não acho que você deva ser capaz de usar um JWT válido para obter um token de atualização. Se você fizer isso, qualquer pessoa pode obter um JWT e manter o acesso a uma conta.

Os tokens de atualização são normalmente retornados com respostas de login / inscrição e, em seguida, o cliente não pode realmente obter acesso a eles novamente para a sessão específica, a menos que faça login / inscreva-se novamente, o que dá a eles uma nova sessão e um novo token de atualização.

Os tokens de atualização não precisam realmente expirar, mas podem ser evocados se estiverem sendo armazenados no banco de dados e, dessa forma, o usuário também pode ver quantas sessões ativas possui. Você pode armazenar mais informações ao emitir tokens de atualização (como sistema operacional, ip, nome do dispositivo etc. para torná-los identificáveis ​​- como o Facebook, GitHub fazem).

Pelo menos é assim que eu faço.

Deve estar tudo bem usar um JWT válido para obter um token de atualização, desde que você verifique a autorização ao emitir o token de atualização e ao tentar usar o token de atualização.

@marshallswain

Atualmente, permitimos obter um novo token postando um token de autenticação válido em <loginEndpoint>/refresh . Os tokens de atualização têm um fluxo de trabalho ligeiramente diferente ...

Portanto, deve ser chamado de renew not refresh para evitar a confusão <loginEndpoint>/renew

Como @abhishekbhardwaj disse

accessToken, não deve ser atualizável por um accessToken, mas apenas por um refresehToken ou nome de usuário / senha, um refreshToken só deve ser atualizável por uma autenticação de usuário / senha ou algum outro segredo, que não seja acessível pelo navegador, como um fator 2 auth ...

Atualmente, é possível atualizar seu accessToken com um accessToken, conforme mencionado aqui:

https://github.com/feathersjs/authentication-jwt/issues/61

Outra abordagem é armazenar o token de atualização dentro da carga útil do accessToken, então a API de atualização atual verifica se o token de atualização não foi revogado (por meio de banco de dados ou chamada de redis). Desta forma, o token de atualização pode ser um ID de incremento automático simples. Além disso, a API de atualização não deve verificar mais a expiração. Como o token de atualização (inteiro simples) é assinado com o token de acesso, é seguro.

Desta forma, as mudanças na base de código atual devem ser mínimas: Para a API de atualização, forneça uma maneira para que o usuário possa fornecer um gancho para verificar se um token de acesso não foi revogado (por meio da verificação do token de atualização dentro de sua carga útil), se este gancho for fornecido don ' t validar mais o tempo de expiração.

Você poderia explicar essa abordagem um pouco mais @ arash16 ?

Se você armazenar o token de atualização na carga útil do accessTokens e a API de atualização não verificar a expiração, você simplesmente não tornou cada accessToken "não expirante"

Porque qualquer accessToken pode ser usado para obter um novo token de acesso, certo?

Estou esquecendo de algo?

@BigAB
Eu quis dizer não verifique a expiração apenas para a API de atualização , o mesmo accessToken é usado como token de atualização e token de acesso. Este token não expira apenas para atualizar e obter um novo token de acesso, o próprio refresh-id pode ser revogado pelo usuário manualmente .

O desenvolvedor deve ter um db-table / redis para armazenar todos os ids de atualização. Quando um usuário precisa revogar ou sair de algumas (ou todas) outras sessões, podemos fornecer a ele uma lista de todos os ids de atualização (mais algumas outras informações extras, como navegador ou data de criação, etc.) e ele escolhe remover (assinar -desde) eles seletivamente. Depois disso, uma vez que o token real contendo esses ids de atualização expira, a API de atualização se recusa a fornecer um novo.

O refresh-id dentro do token não é usado na maioria das vezes e a autorização é sem estado até que o token expire, depois disso, podemos ter uma única chamada para db para validar o id de atualização e retornar um novo token de acesso.

O tempo de expiração do token de acesso pode ser curto (menos de 10 minutos), o usuário pode fechar a página e ir embora, mais tarde quando abrir a página, o token de acesso já expirou e ele está desconectado. Mas o refresh-id dentro do token tem um tempo de vida muito

Do ponto de vista da segurança, o token de acesso usado desta forma deve ser tratado como uma chave de sessão antiga, com o benefício extra de que não teremos que chamar o banco de dados para validá-lo todas as vezes (apenas uma vez expirado).

@ arash16 , gostei da sua ideia de armazenar o token de atualização dentro do JWT de acesso. Existe algum exemplo, como recuperar este token de atualização no lado do servidor?

Meu problema atual: se o token de acesso expirou, a carga útil não está disponível no contexto do gancho do feather. Eu acho que, uma maneira seria usar o verifyJWT() função de utilidade do @feathersjs/authentication pacote, por exemplo, no início de app.service('authentication').hooks({ before: { create: ... } }) ?

Existe alguma maneira cuidadosa de usar tokens de atualização em penas agora? Algum plano de adicionar suporte para eles?

Olá a todos ,

Parece que ainda não está disponível (ou estou faltando alguma coisa). Você poderia nos informar quando estará disponível? Vejo que há um repositório de tokens de atualização de passaporte disponível. Alguém tentou isso?
https://github.com/fiznool/passport-oauth2-refresh

Olá @daffl ,
Alguém pode me ajudar a entender como o token pode ser atualizado para a estratégia do google? Porque não terei senha para o cenário de login do google?

Obrigado

Outra abordagem é armazenar o token de atualização dentro da carga útil do accessToken

Assim, qualquer pessoa que tenha pelo menos um de seus tokens de acesso expirados será capaz de gerar facilmente uma contagem infinita de novos tokens de acesso ou eu perdi alguma coisa?

Os tokens de atualização devem ser armazenados com segurança no cliente e ninguém, exceto este cliente, deve ter acesso a eles!

@deiucanta, a boa notícia é que mantivemos esse recurso em mente enquanto criamos o [email protected]. Eu não acho que vai demorar muito para que possamos colocá-lo em prática e documentá-lo.

Isso foi postado em 2016. Gente, vocês ainda têm planos de oferecer suporte a esse recurso indispensável?

Não. Você não pode fazer nada com um token expirado. Além disso, os tokens de atualização são muito mais facilmente possíveis no pré-lançamento v4 . Uma entrada do livro de receitas sobre como fazer isso fará parte da versão final.

Além disso, os tokens de atualização são muito mais facilmente possíveis no pré-lançamento v4 .

Existe uma data de lançamento aproximada?

Uma entrada do livro de receitas sobre como fazer isso fará parte da versão final.

Até que este guia seja lançado, você poderia explicar em poucas palavras como isso pode ser feito no pré-lançamento v4?

@daffl ping

Já existe uma maneira oficial de fazer isso? v4 está aqui e não vejo nada na documentação

@daffl, você poderia explicar como isso é possível com o 4.0 e sem hacks para o serviço de autenticação?

@MichaelErmer como solução alternativa, você pode usar qualquer estratégia local ou customizada para renovar o jwt, não é ideal, mas funciona bem para comunicação interna, digamos entre o trabalhador e a API.

function initAuth() {
  return async (ctx) => {
    if (ctx.path !== 'authentication') {
      const [authenticated, accessToken] = await Promise.all([
        ctx.app.get('authentication'),
        ctx.app.authentication.getAccessToken(),
      ]);

      if (!accessToken || !authenticated) {
        const result = await ctx.app.authenticate(apiLocalCreds);
        ctx.params = {
          ...ctx.params,
          ...result,
          headers: { ...(ctx.params.headers || {}), Authorization: result.accessToken },
        };
      } else {
        const { exp } = decode(accessToken);
        const expired = Date.now() / 1000 > exp - 60 * 60;
        if (expired) {
          const result = await ctx.app.authenticate(apiLocalCreds);
          ctx.params = {
            ...ctx.params,
            ...result,
            headers: { ...(ctx.params.headers || {}), Authorization: result.accessToken },
          };
        }
      }
    }
    return ctx;
  };
}

client
  .configure(rest(apiHost).superagent(superagent))
  .configure(auth(authConfig))
  .hooks({ before: [initAuth()] });

Atualmente estou usando este gancho after na v4 authentication , para atualizar meu accessToken após 20 dias ...

`` `` javascript
const {DateTime} = require ('luxon')
const renewAfter = {dias: 20}

module.exports = () => {
retornar contexto assíncrono => {
E se (
context.method === 'criar' &&
context.type === 'após' &&
context.path === 'autenticação' &&
context.data && context.data.strategy === 'jwt' &&
context.result &&
context.result.accessToken) {
// verifique se o token precisa ser renovado
const payload = await context.app.service ('authentication'). verifyAccessToken (context.result.accessToken)
const issueAt = DateTime.fromMillis (payload.iat * 1000)
const renoveAfter = emitidoAt.plus (renoveAfter)
const agora = DateTime.local ()
if (now> renewAfter) {
context.result.accessToken = await context.app.service ('autenticação'). createAccessToken ({sub: payload.sub})
}
}
contexto de retorno
}
}
`` ``

É importante ter este gancho em after e como último gancho, de modo que todas as verificações, etc, tenham passado

Algum plano para integrar tokens de atualização em penas?

Eu concordo com essa pergunta uma mensagem antes.

Também estou me perguntando sobre o fluxo de trabalho do token de atualização. A solução esboçada por @ m0dch3n é uma boa prática? Devemos implementar de outra maneira?

Todo o fluxo de trabalho do refreshToken em minha opção protege apenas um pouco contra os ataques do intermediário, de modo que se o intermediário roubar o accessToken, ele pode pelo menos não atualizá-lo e ter acesso infinito aos recursos.

Ele não protege contra XSS, porque, nesse caso, o invasor é capaz de roubar qualquer coisa armazenada no lado do cliente. Assim também o refreshToken ...

O problema agora é que, se você tornar o tempo de expiração do accessToken muito pequeno (ou seja, 5 minutos), você também terá que atualizá-lo com mais frequência. O intermediário só precisa ouvir durante 5 minutos as solicitações dos clientes para interceptar o refreshToken então ... Se você prolongar a expiração, ele terá mais acesso apenas com o accessToken ...

Honestamente, se algum cliente me disser que seu acesso foi roubado, eu preciso colocar accessToken na lista negra E refreshToken de qualquer maneira para ter certeza. Portanto, sou forçado a fazer uma solicitação de banco de dados em cada solicitação de qualquer maneira.

No meu caso, quando tenho conhecimento de tal caso, coloco na lista negra todos os accessTokens dos últimos 40 dias, porque meus accessTokens têm uma validade de 40 dias ...

Usar a solicitação HTTPS torna os ataques man in the middle realmente difíceis. Você não está usando solicitações HTTPS?

Claro que estou usando https, mas existem 3 possibilidades de roubar o accessToken. O primeiro está no lado do cliente (ou seja, XSS), o segundo no transporte (homem no meio) e o terceiro no lado do servidor.

No cliente e no transporte, sou apenas metade responsável pela segurança e a outra metade é o cliente, que não está totalmente sob o meu controle. Mas posso ajudar o cliente, a evitar riscos de segurança, impossibilitando o XSS e assegurando o transporte com https ...

O objetivo de um refreshToken é tornar a expiração de um accessToken mais curta E não transmitir um token válido mais longo ou infinito em CADA solicitação

Então, a única segurança que ele traz, é que a partir de 100 pedidos, ou seja, você não torna todos os 100 vulneráveis ​​no transporte, mas apenas 1 pedido

Então, basicamente, um homem no meio do ataque não pode ser protegido por um refeshToken e claro, não por um XSS ... Ele só pode ser reduzido, por quantas vezes você transmite este refreshToken ... O custo de transmiti-lo é menor no entanto, o accessToken precisa ser mais válido ...

Acabei de copiar / colar meus comentários do canal do Slack:

Acho que o token de atualização é um recurso de suporte obrigatório e não se trata de renovar automaticamente o token de acesso existente. O token de acesso não tem estado e não será armazenado no lado do servidor. O lado negativo é que é válido para sempre! Quanto mais tempo de token de acesso, mais risco é imposto. Mas se o token de acesso for muito curto, seus usuários terão que fazer login com bastante frequência, o que terá um grande impacto na usabilidade.

É aí que entra o token de atualização, o token usado para atualizar o token de acesso e é um token de longa duração. Quando o token de acesso expirou, o cliente pode usar o token de atualização para obter um novo token de acesso, e esse é o único propósito do token de atualização.

O token de atualização é revogável caso a conta do usuário seja comprometida. E essa é a grande diferença entre token de acesso e token de atualização. Para revogar o token de atualização emitido, o servidor deve armazenar todos os tokens de atualização emitidos. Em outras palavras, o token de atualização tem estado. O servidor precisa saber qual é válido e qual é inválido.

Para implementar adequadamente o token de atualização, precisamos de algum tipo de armazenamento de token para persistir o token de atualização. Também precisamos implementar pelo menos três fluxos:

Validação de token de atualização
Atualizar token de acesso com token de atualização válido
Revogar o token de atualização do usuário comprometido

Existem outras funcionalidades de gerenciamento que também são úteis, como estatísticas de uso de tokens.

Acima está meu entendimento atual sobre como implementar token de atualização. Não é fácil, mas é definitivamente necessário construir um sistema mais seguro.

Acontece que o Feathers já possui todas as funcionalidades / módulos necessários para implementar adequadamente os tokens de atualização:

  1. Loja de tokens de atualização: pode ser facilmente suportada pelo Feathers Service.
  2. Emitir e validar o token de atualização: pode apenas reutilizar o suporte JWT existente com AuthenticationService integrado.

Com base no trabalho realizado por TheSinding (https://github.com/TheSinding/authentication-refresh-token), implementei minha própria versão de refresh-tokens com um serviço personalizado e três ganchos (https://github.com/ jackywxd / feather-refresh-token), que permite funcionalidades básicas de atualização-tokens:

  1. Emita o token de atualização após a autenticação do usuário com sucesso;
  2. Atualizar token de acesso com um token de atualização JWT válido;
  3. Efetue logout do usuário excluindo o token de atualização

Embora aproveite totalmente a base de código existente no Feathres, o esforço real de codificação é mínimo e se integra perfeitamente à arquitetura atual do Feathers. Isso prova que a arquitetura atual do Feathers é muito extensível.

Mas um recurso completo do token de atualização também requer suporte no lado do cliente, como armazenar o token de atualização no lado do cliente, reAutenticar o usuário após a expiração do token de acesso, fazer logout do usuário com o token de atualização.

Depois de revisar o código-fonte de penas-autenticação e autenticação-cliente, acredito que o refresh-token pode ser aproveitado no código de recursos existente para permitir a ativação do suporte para o token de atualização tão fácil quanto ativar a autenticação.

Eu já portei minha base de código de atualização-token de versão de ganchos para @ penasjs / autenticação. Em seguida, tentaria fazer alterações no cliente de autenticação para habilitar os recursos do lado do cliente. Meu objetivo final é habilitar o suporte a token de atualização tanto no lado do servidor quanto no lado do cliente.

Minha pergunta / preocupação é como o token de atualização seria armazenado no cliente?

Consulte https://auth0.com/blog/securing-single-page-applications-with-refresh-token-rotation/

Infelizmente, RTs de longa duração não são adequados para SPAs porque não existe um mecanismo de armazenamento persistente em um navegador que pode garantir o acesso apenas pelo aplicativo pretendido. Como existem vulnerabilidades que podem ser exploradas para obter esses artefatos de alto valor e conceder a atores mal-intencionados acesso a recursos protegidos, o uso de tokens de atualização em SPAs foi fortemente desencorajado.

Veja https://afteracademy.com/blog/implement-json-web-token-jwt-authentication-using-access-token-and-refresh-token

Então, qual é o melhor lugar possível para armazenar os tokens com segurança? Você pode ler mais sobre isso na Internet se quiser obter um armazenamento totalmente seguro. Algumas das soluções são ideais, mas não muito práticas. Praticamente eu o armazenaria nos Cookies com sinalizadores httpOnly e Secure. Não é 100 por cento seguro, mas realiza o trabalho.

Veja esta longa discussão sobre cookies - https://github.com/feathersjs-ecosystem/authentication/issues/132 também

@bwgjoseph Eu sugeriria usar uma sessão expressa regular e armazenar todos os tokens lá, em vez do lado do cliente. Isso é o que eu faço e funciona perfeitamente bem com todos os tipos de aplicativos, incluindo SPA

@sarkistlt Você quer dizer para armazenar todos os tokens JWT do cliente no lado do servidor? Algum material de referência / artigo para isso? Não tenho certeza de como seria o processo. Então, o que o cliente envia ao solicitar dados (CRUD)?

@bwgjoseph o mesmo de sempre, cookie, basta adicionar middlewere antes de registrar seus serviços:

app.use('* | [or specific rout]', session(sess), (req, res, next) => {
      req.feathers.session = req.session || {};
      next();
    });

então, em seu servidor, para digamos serviço de login do cliente, quando o cliente é autenticado, você apenas armazena o token na sessão como ctx.params.session.token = token , onde o token é o seu acesso JWT ou token de atualização, depende da lógica do seu aplicativo.
E com qualquer nova solicitação do cliente, você verificará se o token existe na sessão e o usará para autenticação. Essa é uma abordagem muito mais segura, pois nenhum dos tokens é exposto no lado do cliente.

Vou apenas acrescentar que isso funciona melhor para aplicativos cliente (navegador) - servidor. Ao se comunicar internamente entre servidores, ou trabalhador / servidor, você não precisa de sessão.

Isso foi discutido _muito_ antes (eu também adicionei uma entrada ao FAQ ) e não é necessariamente mais seguro armazenar um token em uma sessão. Se alguém obtiver acesso à sua página para executar scripts, essa pessoa também sequestrou a sessão e poderá fazer solicitações autenticadas de qualquer maneira.

Portanto, geralmente não há problema em armazenar um token, por exemplo, localStorage (ao qual apenas a página atual também tem acesso) e também funciona perfeitamente com outras plataformas que não sejam de navegador (como aplicativos móveis nativos, servidor para servidor etc.) __and websockets__ (não consigo enfatizar o quão doloroso é fazer os websockets funcionarem perfeitamente e com segurança com cookies HTTP - minha vida tem sido muito mais fácil desde que paramos de tentar fazer isso). Em geral, um token de atualização deve ser revogável, pois geralmente tem uma vida útil muito mais longa.

De qualquer forma, uma solicitação de pull para isso seria muito bem-vinda, acho que torna muito mais fácil acertar os detalhes.

@jackywxd - Ótimo trabalho, dei uma olhada rápida nele e parece que foram ótimas adições.
Existe alguma maneira de tornar mais fácil para o desenvolvedor implementar isso?
Como integrar os ganchos que você criou à biblioteca, então não precisaríamos adicionar os ganchos mais tarde?

Acho que você deve criar uma solicitação pull e poderíamos ter uma discussão lá.

@daffl sim concordo, especialmente com WS. E se o cliente e o back-end forem construídos por você ou sua equipe, sim, é melhor apenas usar o JWT e evitar dependências extras e complexidade em seu aplicativo.
Mas, em alguns casos, por exemplo, ao construir a vitrine REST-API que será usada por empresas / desenvolvedores terceirizados, é mais fácil usar uma sessão regular e pedir aos desenvolvedores que incluam credenciais com suas solicitações do que descrever como recuperar o acesso (e atualizar ) tokens, armazene-os e transmita-os com cada solicitação. O que foi feito perfeitamente pelo cliente do feather, mas na maioria dos casos, quando o desenvolvedor não está familiarizado com a construção do back-end, eles usarão request, superagent, fetch or axios para conectar seu aplicativo ao back-end. Pelo menos no meu caso, este foi o principal motivo para mover a parte da vitrine da API para trabalhar com sessões regulares em vez de JWT diretamente.

Mas não seria essa a decisão de design e responsabilidade, do criador da referida loja, implementar em sua própria API e, em seguida, documentar adequadamente esse recurso, em vez de "forçar" essa decisão para a comunidade?

@TheSinding Acho que podemos dizer 'forçar' sobre a abordagem menos comumente usada, não sobre o cookie que foi (e ainda é) a abordagem mais comumente usada para gerenciar sessões de usuário.

E sim, é uma decisão de design feita após o feedback dos desenvolvedores que têm usado a API. Quando você está executando um sistema multilocatário ou API usado por várias equipes, às vezes é melhor seguir a prática geral da indústria para evitar confusão adicional e tempo extra gasto pelos desenvolvedores, especialmente se a solução alternativa não fornecer nenhuma vantagem para o usuário final.

Mais uma vez, para ficar claro, usar JWT é ótimo e muito mais fácil de utilizar, especialmente para API em tempo real e é isso que estamos usando em 90% dos casos + você não precisa executar o redis ou qualquer outra coisa para gerenciar sessões de cookies.
Mas há alguns casos excepcionais em que pode não ser o melhor escolhido, eu trouxe um exemplo dessa situação em meu comentário anterior.

Eu não estava dizendo que você estava errado, só estava pensando "em voz alta" :)

IMO, acho que a abordagem com JWT seria uma abordagem melhor, uma vez que é o que já é suportado e como @daffl também é mais fácil de trabalhar com

Obrigado por todos os seus comentários! JWT vs sessão é um tópico diferente que podemos discutir separadamente.

Acho que uma coisa que todos concordamos é que token de acesso + token de atualização é uma solução muito mais segura do que apenas token de acesso. Ele foi amplamente adotado pelos principais gigantes da Internet. É justo dizer que a comunidade Feathers adoraria ver a base de código principal do Feathers para fornecer suporte integrado para token de atualização. Na verdade, fiquei surpreso quando percebi que o Feathers não oferece suporte a token de atualização.

Tenho usado o AWS cognito em alguns de meus projetos, o AWS Amplify salvará três tokens no localStorage: token de ID, token de acesso e token de atualização, Amplify lida com todos os trabalhos sujos relacionados ao gerenciamento de token e fornece algumas APIs que permitem fácil e interface direta trabalhando com o back-end do Cognito. Eu gostaria de ver uma experiência de desenvolvimento semelhante com Feathers.

@TheSinding Obrigado por seu excelente trabalho anterior!

Como a autenticação existente é implementada como serviço normal, poderíamos facilmente habilitar o suporte para token de atualização estendendo a classe e adicionando ganchos:

this.hooks({ after: { create: [issueRefreshToken(), connection('login'), event('login')], remove: [logoutUser(), connection('logout'), event('logout')], patch: [refreshAccessToken()], },

Assim como ativamos a autenticação usando a CLI, poderíamos oferecer a opção semelhante na CLI para suporte a token de atualização. o desenvolvedor pode simplesmente responder SIM, o CLI cria automaticamente um serviço de "atualização de tokens" do cliente e atualiza as configurações relacionadas ao token de atualização no arquivo de configuração padrão. Portanto, é como uma solução pronta para o uso para desenvolvedores.

@daffl David, obrigado por criar Feathers! apenas imaginando se existe algum "guia de contribuição", "diretriz de codificação", "guia de estilo" para Feathers?

Como mencionei antes, um PR com a implementação de tokens de atualização (ou mesmo apenas algumas ideias) seria muito bem-vindo. Ainda não tive a chance de verificar os repositórios vinculados e essa discussão está ficando muito longa. Ter tudo atualizado e em um só lugar tornaria as coisas muito mais fáceis. Alguns pontos importantes:

  • Os tokens de atualização podem ser emitidos estendendo o serviço de autenticação
  • Os tokens de atualização devem ser armazenados em localStorage
  • Os tokens de atualização precisam ser revogáveis ​​(portanto, deve haver uma implementação de propósito mais geral do mecanismo de revogação descrito em https://docs.feathersjs.com/cookbook/authentication/revoke-jwt.html)
  • Devido à complexidade e configuração adicionais (como Redis para armazenar tokens revogados), ele pode estar disponível como um recurso, mas não deve ser habilitado por padrão (ou seja, um aplicativo gerado padrão - pode ser parte da CLI, mas antes de fazer qualquer coisa nisso, Ainda estou procurando ajuda para iniciar um gerador baseado em Hygen )
  • As informações de contribuição podem ser encontradas no guia do contribuidor

Se alguém estiver usando o refreshToken, criamos uma biblioteca que lida com front-end, accessToken e refreshToken como "sessões", muito fácil de usar.

Só precisa passar os tokens e a biblioteca tenta obter um novo acessToken com refreshToken quando expirará. Atualmente é usado no Videsk .

Repositório: Front Auth Handler

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

rrubio picture rrubio  ·  4Comentários

corymsmith picture corymsmith  ·  4Comentários

jordanbtucker picture jordanbtucker  ·  4Comentários

perminder-klair picture perminder-klair  ·  3Comentários

harrytang picture harrytang  ·  3Comentários