Invalidando Tokens da Web JSON

421

Para um novo projeto node.js em que estou trabalhando, estou pensando em mudar de uma abordagem de sessão baseada em cookie (com isso, quero dizer, armazenar um ID em um armazenamento de valores-chave que contém sessões do usuário no navegador do usuário) para uma abordagem de sessão baseada em token (sem armazenamento de valores-chave) usando JSON Web Tokens (jwt).

O projeto é um jogo que utiliza o socket.io - ter uma sessão baseada em token seria útil em um cenário em que haverá vários canais de comunicação em uma única sessão (web e socket.io)

Como fornecer uma invalidação de token / sessão do servidor usando a abordagem jwt?

Eu também queria entender que armadilhas / ataques comuns (ou incomuns) eu deveria procurar com esse tipo de paradigma. Por exemplo, se esse paradigma estiver vulnerável aos mesmos / diferentes tipos de ataques que a abordagem de armazenamento de sessão / cookie.

Então, digamos que tenho o seguinte (adaptado disso e deste ):

Login da Loja de Sessão:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

Login baseado em token:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

-

Um logout (ou invalidação) da abordagem de Armazenamento de Sessão exigiria uma atualização no banco de dados KeyValueStore com o token especificado.

Parece que esse mecanismo não existiria na abordagem baseada em token, pois o próprio token conteria as informações que normalmente existiriam no armazenamento de valores-chave.

funseiki
fonte
1
Se você estiver usando o pacote 'express-jwt', poderá dar uma olhada na isRevokedopção ou tentar replicar a mesma funcionalidade. github.com/auth0/express-jwt#revoked-tokens
Signus
1
Considere usar um tempo de expiração curto no token de acesso e um token de atualização, com expiração de longa duração, para permitir a verificação do status de acesso do usuário em um banco de dados (lista negra). auth0.com/blog/…
Rohmer
outra opção seria anexar o endereço IP na carga útil ao gerar o token jwt e verificar o IP armazenado versus a solicitação de entrada para o mesmo endereço IP. ex: req.connection.remoteAddress em nodeJs. Existem provedores de ISP que não emitem IP estático por cliente, acho que isso não será um problema, a menos que um cliente se reconecte à Internet.
Gihan Sandaru

Respostas:

392

Eu também tenho pesquisado essa questão e, embora nenhuma das idéias abaixo seja uma solução completa, elas podem ajudar outras pessoas a descartar idéias ou fornecer outras.

1) Simplesmente remova o token do cliente

Obviamente, isso não faz nada pela segurança do servidor, mas impede um invasor removendo o token da existência (ou seja, eles teriam que ter roubado o token antes do logout).

2) Crie uma lista negra de tokens

Você pode armazenar os tokens inválidos até a data de vencimento inicial e compará-los com as solicitações recebidas. No entanto, isso parece negar o motivo de obter um token completo, pois você precisaria tocar no banco de dados para cada solicitação. O tamanho do armazenamento provavelmente seria menor, pois você só precisaria armazenar tokens que estavam entre o logout e o tempo de expiração (esse é um pressentimento e definitivamente depende do contexto).

3) Apenas mantenha curtos os prazos de validade dos tokens e gire-os com frequência

Se você mantiver os tempos de expiração do token em intervalos curtos o suficiente e pedir ao cliente em execução que acompanhe e solicite atualizações quando necessário, o número 1 funcionaria efetivamente como um sistema de logout completo. O problema com esse método é que torna impossível manter o usuário conectado entre os fechamentos do código do cliente (dependendo de quanto tempo você faz o intervalo de expiração).

Planos de contingência

Se alguma vez houve uma emergência ou um token de usuário foi comprometido, uma coisa que você pode fazer é permitir que o usuário altere um ID de pesquisa de usuário subjacente com suas credenciais de logon. Isso tornaria todos os tokens associados inválidos, pois o usuário associado não poderia mais ser encontrado.

Também gostaria de observar que é uma boa ideia incluir a data do último login com o token, para que você possa impor um novo logon após um período distante.

Em termos de semelhanças / diferenças com relação a ataques usando tokens, esta postagem aborda a questão: https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown

Matt Way
fonte
3
Excelente abordagem. Meu instinto seria fazer uma combinação de todos os 3 e / ou solicitar um novo token após cada "n" pedido (em vez de um timer). Estamos usando redis para armazenamento de objetos na memória e poderíamos usá-lo facilmente no caso 2, e a latência diminuiria MUITO.
Aaron Wagner
2
Este post de horror sobre codificação oferece alguns conselhos: mantenha os cookies (ou tokens) de sessão curtos, mas torne-os invisíveis para o usuário - o que parece estar alinhado com o # 3. Meu próprio intestino (talvez porque é mais tradicional) é apenas para ter o ato simbólico (ou um hash dela) como uma chave no banco de dados sessão de branco-listados (semelhante ao # 2)
funseiki
8
O artigo está bem escrito e é uma versão elaborada 2)acima. Embora funcione bem, pessoalmente não vejo muita diferença nas lojas de sessões tradicionais. Eu acho que o requisito de armazenamento seria menor, mas você ainda precisa de um banco de dados. O maior apelo do JWT para mim foi não usar um banco de dados para sessões.
Matt Way
211
Uma abordagem comum para invalidar tokens quando um usuário altera sua senha é assinar o token com um hash da senha. Portanto, se a senha for alterada, quaisquer tokens anteriores falharão automaticamente na verificação. Você pode estender isso para fazer logoff incluindo um horário de último logout no registro do usuário e usando uma combinação do hash do último horário de logoff e da senha para assinar o token. Isso requer uma pesquisa no banco de dados toda vez que você precisa verificar a assinatura do token, mas presumivelmente você está procurando o usuário de qualquer maneira.
Travis Terry
4
Uma lista negra pode ser eficiente mantendo-a na memória, para que o banco de dados precise ser atingido apenas para registrar invalidações e remover as invalidações expiradas e apenas ler na inicialização do servidor. Sob uma arquitetura de balanceamento de carga, a lista negra da memória pode pesquisar o banco de dados em intervalos curtos, como 10s, limitando a exposição de tokens invalidados. Essas abordagens permitem que o servidor continue autenticando solicitações sem acessos de banco de dados por solicitação.
Joe Lapp
86

As idéias postadas acima são boas, mas uma maneira muito simples e fácil de invalidar todas as JWTs existentes é simplesmente mudar o segredo.

Se o servidor criar o JWT, assiná-lo com um segredo (JWS) e enviá-lo ao cliente, simplesmente alterar o segredo invalidará todos os tokens existentes e exigirá que todos os usuários obtenham um novo token para autenticar, pois seu token antigo se torna subitamente inválido de acordo com para o servidor.

Não requer nenhuma modificação no conteúdo real do token (ou ID da pesquisa).

Claramente, isso só funciona para um caso de emergência quando você deseja que todos os tokens existentes expirem. Para expirar por token, é necessária uma das soluções acima (como tempo de expiração curto do token ou invalidar uma chave armazenada dentro do token).

Andy
fonte
9
Eu acho que essa abordagem não é ideal. Embora funcione e seja certamente simples, imagine um caso em que você esteja usando uma chave pública - você não gostaria de recriar essa chave sempre que desejar invalidar um único token.
Signus 30/05
1
@KijanaWoodard, um par de chaves pública / privada pode ser usado para validar a assinatura, assim como o segredo do algoritmo RS256. No exemplo mostrado aqui, ele menciona a alteração do segredo para invalidar um JWT. Isso pode ser feito: a) introduzindo um pubkey falso que não corresponde à assinatura ou b) gerando um novo pubkey. Nessa situação, é menos que o ideal.
Signus
1
@Signus - peguei. não usar a chave pública como segredo, mas outros podem confiar na chave pública para verificar a assinatura.
Kijana Woodard
8
Esta é uma solução muito ruim. O principal motivo para usar o JWT é o estado sem estado e a escala. O uso de um segredo dinâmico introduz um estado. Se o serviço estiver agrupado em vários nós, você precisará sincronizar o segredo sempre que um novo token for emitido. Você precisaria armazenar segredos em um banco de dados ou em outro serviço externo, o que seria apenas uma reinvenção da autenticação baseada em cookies.
Tuomas Toivonen
5
@TuomasToivonen, mas você deve assinar um JWT com um segredo e poder verificar o JWT com o mesmo segredo. Portanto, você deve armazenar o segredo nos recursos protegidos. Se o segredo estiver comprometido, você deverá alterá-lo e distribuir essa alteração para cada um de seus nós. Os provedores de hospedagem com armazenamento em cluster / escalonamento geralmente permitem armazenar segredos em seus serviços para tornar a distribuição desses segredos fácil e confiável.
Rohmer
67

Este é principalmente um longo comentário que apóia e desenvolve a resposta de @mattway

Dado:

Algumas das outras soluções propostas nesta página defendem que o armazenamento de dados seja atingido em todas as solicitações. Se você acessar o armazenamento de dados principal para validar todas as solicitações de autenticação, vejo menos motivos para usar o JWT em vez de outros mecanismos de autenticação de token estabelecidos. Você essencialmente tornou o JWT com estado, em vez de sem estado, se você for ao armazenamento de dados todas as vezes.

(Se o seu site receber um grande volume de solicitações não autorizadas, a JWT as negará sem acessar o armazenamento de dados, o que é útil. Provavelmente existem outros casos de uso como esse.)

Dado:

A autenticação JWT verdadeiramente sem estado não pode ser alcançada para um aplicativo Web típico do mundo real, porque o JWT sem estado não tem uma maneira de fornecer segurança imediata e segura suporte para os seguintes casos de uso importantes:

A conta do usuário é excluída / bloqueada / suspensa.

A senha do usuário foi alterada.

As funções ou permissões do usuário são alteradas.

Usuário desconectado pelo administrador.

Quaisquer outros dados críticos do aplicativo no token JWT são alterados pelo administrador do site.

Você não pode esperar pela expiração do token nesses casos. A invalidação do token deve ocorrer imediatamente. Além disso, você não pode confiar no cliente para não manter e usar uma cópia do token antigo, com intenção maliciosa ou não.

Portanto: acho que a resposta de @ matt-way, # 2 TokenBlackList, seria a maneira mais eficiente de adicionar o estado necessário à autenticação baseada em JWT.

Você tem uma lista negra que contém esses tokens até a data de vencimento. A lista de tokens será muito pequena em comparação com o número total de usuários, pois ele só precisa manter os tokens na lista negra até sua expiração. Eu implementaria colocando tokens inválidos em redis, memcached ou outro armazenamento de dados na memória que suporta a definição de um tempo de expiração em uma chave.

Você ainda precisa fazer uma chamada para o banco de dados na memória para cada solicitação de autenticação que passa a autenticação JWT inicial, mas não precisa armazenar chaves para todo o conjunto de usuários. (O que pode ou não ser importante para um determinado site.)

Ed J
fonte
15
Eu não concordo com sua resposta. Atingir um banco de dados não torna nada com estado; estado de armazenamento no seu back-end faz. O JWT não foi criado para que você não precise acessar o banco de dados em cada solicitação. Todo aplicativo principal que usa o JWT é apoiado por um banco de dados. O JWT resolve um problema completamente diferente. en.wikipedia.org/wiki/Stateless_protocol
Julian
6
@ Julian, você pode falar um pouco sobre isso? Que problema o JWT realmente resolve então?
zero01alpha
8
@ zero01alpha Autenticação: este é o cenário mais comum para usar o JWT. Após o login do usuário, cada solicitação subsequente incluirá o JWT, permitindo que o usuário acesse rotas, serviços e recursos permitidos com esse token. Troca de informações: os JSON Web Tokens são uma boa maneira de transmitir informações com segurança entre as partes. Como as JWTs podem ser assinadas, você pode ter certeza de que os remetentes são quem eles dizem que são. Veja jwt.io/introduction
Julian
7
@ Julian Não concordo com sua discordância :) A JWT resolve o problema (para serviços) de ter a necessidade de acessar uma entidade centralizada que fornece informações de autorização para qualquer cliente. Portanto, em vez do serviço A e do serviço B precisam acessar algum recurso para descobrir se o cliente X tem ou não permissões para fazer algo, os serviços A e B recebem um token do X que comprova suas permissões (normalmente emitidas por terceiros) festa). De qualquer forma, o JWT é uma ferramenta que ajuda a evitar um estado compartilhado entre serviços em um sistema, especialmente quando eles são controlados por mais de um provedor de serviços.
Livanov
1
Também de jwt.io/introduction If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case.
giantas 14/12/19
43

Eu manteria um registro do número da versão do jwt no modelo do usuário. Os novos tokens jwt definiriam sua versão para isso.

Ao validar o jwt, basta verificar se ele possui um número de versão igual à versão atual do jwt dos usuários.

Sempre que você desejar invalidar jwts antigos, basta aumentar o número da versão do jwt dos usuários.

DaftMonk
fonte
15
Essa é uma ideia interessante, a única coisa é onde armazenar a versão, pois parte do objetivo dos tokens é ser sem estado e não precisar usar o banco de dados. Uma versão codificada dificultaria a execução e um número de versão em um banco de dados negaria alguns dos benefícios do uso de tokens.
Stephen Smith
13
Presumivelmente, você já está armazenando uma identificação de usuário no seu token e consultando o banco de dados para verificar se o usuário existe / está autorizado a acessar o terminal da API. Portanto, você não está fazendo nenhuma consulta extra ao comparar o número da versão do token jwt com o número do usuário.
DaftMonk 7/07
5
Eu não deveria dizer, presumivelmente, porque existem muitas situações em que você pode usar tokens com validações que não tocam no banco de dados. Mas acho que neste caso é difícil evitar.
DaftMonk 7/07
11
E se o usuário efetuar login de vários dispositivos? Um token deve ser usado em todos eles ou o login deve invalidar todos os anteriores?
precisa saber é o seguinte
10
Concordo com o @SergioCorrea Isso tornaria o JWT quase tão estável quanto qualquer outro mecanismo de autenticação de token.
Ed J
40

Ainda não tentei isso, e usa muitas informações com base em algumas das outras respostas. A complexidade aqui é evitar uma chamada de armazenamento de dados no servidor por solicitação de informações do usuário. A maioria das outras soluções requer uma pesquisa de banco de dados por solicitação em um armazenamento de sessão do usuário. Isso é bom em certos cenários, mas isso foi criado na tentativa de evitar essas chamadas e tornar o estado do lado do servidor necessário muito pequeno. Você acabará recriando uma sessão do lado do servidor, por menor que seja, para fornecer todos os recursos de invalidação de força. Mas se você quiser fazer isso aqui está a essência:

Metas:

  • Atenuar o uso de um armazenamento de dados (sem estado).
  • Capacidade de forçar o logoff de todos os usuários.
  • Capacidade de forçar o logout de qualquer indivíduo a qualquer momento.
  • Capacidade de exigir a reinserção de senha após um certo período de tempo.
  • Capacidade de trabalhar com vários clientes.
  • Capacidade de forçar um novo logon quando um usuário clica em logout de um cliente específico. (Para impedir que alguém "exclua" um token de cliente depois que o usuário se afasta - consulte os comentários para obter informações adicionais)

A solução:

  • Use tokens de acesso de curta duração (<5m) emparelhados com um token de atualização armazenado por cliente de vida mais longa (algumas horas) .
  • Cada solicitação verifica a validade ou validade do token de autenticação ou atualização.
  • Quando o token de acesso expira, o cliente usa o token de atualização para atualizar o token de acesso.
  • Durante a verificação do token de atualização, o servidor verifica uma pequena lista negra de IDs de usuário - se encontrado, rejeita a solicitação de atualização.
  • Quando um cliente não possui um token de atualização ou autenticação válido (não expirado), o usuário deve efetuar login novamente, pois todas as outras solicitações serão rejeitadas.
  • Na solicitação de login, verifique se há ban no armazenamento de dados do usuário.
  • No logout - adicione esse usuário à lista negra da sessão para que ele tenha que efetuar login novamente. Você precisaria armazenar informações adicionais para não desconectá-los de todos os dispositivos em um ambiente de vários dispositivos, mas isso poderia ser feito adicionando um campo de dispositivo ao lista negra de usuários.
  • Para forçar a reentrada após x período de tempo - mantenha a data do último login no token de autenticação e verifique-o por solicitação.
  • Para forçar o logoff de todos os usuários - redefina a chave de hash do token.

Isso requer que você mantenha uma lista negra (estado) no servidor, supondo que a tabela do usuário contenha informações banidas do usuário. A lista negra de sessões inválidas - é uma lista de IDs de usuário. Essa lista negra é verificada apenas durante uma solicitação de atualização de token. As entradas são necessárias para permanecer nele, desde que o token de atualização TTL. Depois que o token de atualização expirar, o usuário deverá fazer login novamente.

Contras:

  • Ainda é necessário fazer uma consulta ao armazenamento de dados na solicitação de atualização do token.
  • Os tokens inválidos podem continuar a operar para o TTL do token de acesso.

Prós:

  • Fornece a funcionalidade desejada.
  • A ação de atualização do token está oculta do usuário em operação normal.
  • Necessário apenas para fazer uma pesquisa no armazenamento de dados em solicitações de atualização, em vez de em todas as solicitações. ou seja, 1 a cada 15 minutos, em vez de 1 por segundo.
  • Minimiza o estado do lado do servidor para uma lista negra muito pequena.

Com esta solução, não é necessário um armazenamento de dados na memória como reddis, pelo menos não para informações do usuário, pois você é o servidor que faz apenas uma chamada db a cada 15 minutos. Se estiver usando reddis, armazenar uma lista de sessões válida / inválida seria uma solução muito mais rápida e simples. Não há necessidade de um token de atualização. Cada token de autenticação teria uma identificação de sessão e um dispositivo, eles poderiam ser armazenados em uma tabela reddis na criação e invalidados quando apropriado. Eles seriam verificados em cada solicitação e rejeitados quando inválidos.

Ashtonian
fonte
E o cenário em que uma pessoa se levanta de um computador para permitir que outra pessoa use o mesmo computador? A 1ª pessoa sairá e espera que a saída bloqueie instantaneamente a 2ª pessoa. Se a segunda pessoa for um usuário médio, o cliente poderá facilmente bloquear o usuário excluindo o token. Mas se o segundo usuário tiver habilidades de hackers, ele terá tempo para recuperar o token ainda válido para se autenticar como o primeiro usuário. Parece que não há como evitar a necessidade de invalidar imediatamente os tokens, sem demora.
21816 Joe Lapp
5
Ou você pode remover seu JWT da sessão / armazenamento local ou cookie.
21916 Kamil Kiełczewski
1
Obrigado @Ashtonian. Depois de fazer uma extensa pesquisa, abandonei os JWTs. A menos que você faça esforços extraordinários para proteger a chave secreta ou a menos que você delegue para uma implementação segura do OAuth, as JWTs são muito mais vulneráveis ​​do que as sessões regulares. Veja meu relatório completo: by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
Joe Lapp
2
Usar um token de atualização é a chave para permitir a lista negra. Ótima explicação: auth0.com/blog/…
Rohmer
1
Essa me parece a melhor resposta, pois combina um token de acesso de curta duração com um token de atualização de longa duração que pode ser colocado na lista negra. No logout, o cliente deve excluir o token de acesso para que um segundo usuário não possa obter acesso (mesmo que o token de acesso permaneça válido por mais alguns minutos após o logout). @ Joe Lapp diz que um hacker (segundo usuário) obtém o token de acesso mesmo depois de ter sido excluído. Quão?
M3RS
14

Uma abordagem que tenho considerado é sempre ter um valor iat(emitido em) no JWT. Em seguida, quando um usuário fizer logoff, armazene esse carimbo de data / hora no registro do usuário. Ao validar o JWT, basta comparar o iatregistro de data e hora com o último logoff. Se o iatfor mais antigo, não é válido. Sim, você precisa acessar o banco de dados, mas sempre processarei o registro do usuário se o JWT for válido.

A principal desvantagem que vejo disso é que os desconectaria de todas as sessões se estiverem em vários navegadores ou também tiver um cliente móvel.

Esse também pode ser um bom mecanismo para invalidar todas as JWTs em um sistema. Parte da verificação pode ser contra um registro de data e hora global do último iathorário válido .

Brack Mo
fonte
1
Bom pensamento! Resolver o problema de "um dispositivo" é tornar isso um recurso de contingência e não um logout. Armazene a data a no registro do usuário que invalide todos os tokens emitidos antes dele. Algo como token_valid_after, ou algo assim. Impressionante!
OneHoopyFrood
1
Ei, @OneHoopyFrood, você tem um código de exemplo para me ajudar a entender a ideia de uma maneira melhor? Eu realmente aprecio sua ajuda!
Alexventuraio
2
Como todas as outras soluções propostas, esta requer uma pesquisa no banco de dados, razão pela qual esta pergunta existe, pois evitar essa pesquisa é a coisa mais importante aqui! (Desempenho, escalabilidade). Em circunstâncias normais, você não precisa de uma pesquisa de banco de dados para obter os dados do usuário, você já os obteve do cliente.
Rob Evans
9

Estou um pouco atrasado aqui, mas acho que tenho uma solução decente.

Eu tenho uma coluna "last_password_change" no meu banco de dados que armazena a data e a hora em que a senha foi alterada pela última vez. Também guardo a data / hora da emissão no JWT. Ao validar um token, verifico se a senha foi alterada após a emissão do token e se o token foi rejeitado, mesmo que ainda não tenha expirado.

Matas Kairaitis
fonte
1
Como você rejeita o token? Você pode mostrar um breve código de exemplo?
Alexventuraio
1
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
Vanuan
15
Requer uma pesquisa de banco de dados!
Rob Evans
5

Você pode ter um campo "last_key_used" no seu banco de dados no documento / registro do usuário.

Quando o usuário efetua login com user e passa, gere uma nova sequência aleatória, armazene-a no campo last_key_used e adicione-a à carga útil ao assinar o token.

Quando o usuário efetua login usando o token, verifique o last_key_used no banco de dados para corresponder ao do token.

Então, quando o usuário fizer um logout, por exemplo, ou se você desejar invalidar o token, basta alterar o campo "last_key_used" para outro valor aleatório e qualquer verificação subsequente falhará, forçando o usuário a efetuar login com o usuário e passar novamente.

NickVarcha
fonte
Esta é a solução que eu estou considerando, mas tem as seguintes desvantagens: (1) você está fazendo uma consulta ao banco de dados em cada solicitação para verificar o aleatório (anulando o motivo do uso de tokens em vez de sessões) ou está somente verificando intermitentemente após a expiração de um token de atualização (impedindo que os usuários efetuem logout imediatamente ou que as sessões sejam encerradas imediatamente); e (2) fazer logoff efetua logout do usuário em todos os navegadores e dispositivos (o que não é um comportamento convencionalmente esperado).
21416 Joe Lapp
Você não precisa mudar a chave quando o usuário fizer fora, apenas quando eles mudar sua senha ou -Se você fornecer ele- quando eles escolhem para registro de todos os dispositivos
NickVarcha
3

Mantenha uma lista na memória como esta

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

Se seus tokens expirarem em uma semana, limpe ou ignore os registros anteriores. Mantenha também apenas o registro mais recente de cada usuário. O tamanho da lista dependerá de quanto tempo você mantém seus tokens e com que frequência os usuários os revogam. Use db somente quando a tabela mudar. Carregue a tabela na memória quando o aplicativo iniciar.

Eduardo
fonte
2
Como a maioria dos sites de produção é executada em mais de um servidor, essa solução não funciona. A adição de Redis ou cache de inter-processos semelhante complica significativamente o sistema e geralmente traz mais problemas do que soluções.
user2555515
@ user2555515 todos os servidores podem ser sincronizados com o banco de dados. É sua escolha atingir o banco de dados toda vez ou não. Você poderia dizer que problemas isso traz.
Eduardo
3

------------------------ Tarde demais para esta resposta, mas pode ser que isso ajude alguém ------------- -----------

No lado do cliente , a maneira mais fácil é remover o token do armazenamento do navegador.

Mas, e se você quiser destruir o token no servidor Node -

O problema com o pacote JWT é que ele não fornece nenhum método ou maneira de destruir o token. Você pode usar métodos diferentes em relação ao JWT mencionados acima. Mas aqui vou eu com o jwt-redis.

Portanto, para destruir o token no lado do servidor, você pode usar o pacote jwt-redis em vez do JWT

Essa biblioteca (jwt-redis) repete completamente toda a funcionalidade da biblioteca jsonwebtoken, com uma adição importante. Jwt-redis permite armazenar o rótulo do token em redis para verificar a validade. A ausência de um rótulo de token em redis torna o token inválido. Para destruir o token no jwt-redis, existe um método de destruição

funciona desta maneira:

1) Instale o jwt-redis a partir do npm

2) Para criar -

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3) Para verificar -

jwtr.verify(token, secret);

4) Destruir -

jwtr.destroy(token)

Nota : você pode fornecer expiresIn durante o logon do token da mesma forma que é fornecido no JWT.

Pode ser que isso ajude alguém

Aman Kumar Gupta
fonte
2

Por que não usar apenas a declaração jti (nonce) e armazená-la em uma lista como um campo de registro do usuário (dependente de db, mas pelo menos uma lista separada por vírgula é adequada)? Não há necessidade de pesquisa separada, pois outros indicaram que você deseja obter o registro do usuário de qualquer maneira e, dessa forma, você pode ter vários tokens válidos para diferentes instâncias do cliente ("logout em qualquer lugar" pode redefinir a lista para vazia)

davidkomer
fonte
Sim este. Talvez faça uma relação um-para-muitos entre a tabela do usuário e uma nova tabela (sessão), para que você possa armazenar metadados junto com as declarações jti.
Peter Lada
2
  1. Dê um prazo de validade de 1 dia para os tokens
  2. Mantenha uma lista negra diária.
  3. Coloque os tokens inválidos / de logout na lista negra

Para validação de token, verifique primeiro o tempo de expiração do token e, em seguida, a lista negra, se o token não tiver expirado.

Para necessidades de sessões longas, deve haver um mecanismo para estender o tempo de expiração do token.

Ebru Yener
fonte
4
colocar fichas na lista negra e lá vai o seu apatridia
Kerem Baydoğan
2

Tarde para a festa, MEUS dois centavos são dados abaixo depois de algumas pesquisas. Durante o logout, verifique se as seguintes coisas estão acontecendo ...

Limpe o armazenamento / sessão do cliente

Atualize a data e o horário do último logon da tabela do usuário sempre que o logon ou logout ocorrer, respectivamente. Portanto, a hora da data de login sempre deve ser maior que o logout (ou mantenha a data de logout nula se o status atual for logon e ainda não estiver logado)

Isso é muito simples do que manter uma tabela adicional de lista negra e limpar regularmente. O suporte a vários dispositivos requer uma tabela adicional para manter o logon, as datas de logout com alguns detalhes adicionais, como detalhes do SO ou do cliente.

Shamseer
fonte
2

Cadeia de caracteres exclusiva por usuário e cadeia de caracteres global agrupadas

servir como parte secreta da JWT permite a invalidação de token individual e global. Flexibilidade máxima ao custo de uma pesquisa / leitura de banco de dados durante a autenticação de solicitação. Também é fácil de armazenar em cache, pois raramente mudam.

Aqui está um exemplo:

HEADER:ALGORITHM & TOKEN TYPE

{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:DATA

{
  "sub": "1234567890",
  "some": "data",
  "iat": 1516239022
}
VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  HMACSHA256('perUserString'+'globalString')
)

where HMACSHA256 is your local crypto sha256
  nodejs 
    import sha256 from 'crypto-js/sha256';
    sha256(message);

por exemplo, use https://jwt.io (não tenho certeza de que eles lidam com segredos dinâmicos de 256 bits)

Mark Essel
fonte
1
Alguns mais detalhes seria suficiente
giantas
2
@ Giantas, acho que o que Mark quer dizer é a parte da assinatura. Portanto, em vez de usar apenas uma chave para assinar o JWT, combine uma chave exclusiva para cada cliente. Portanto, se você deseja invalidar a sessão completa de um usuário, basta alterar a chave desse usuário e, se você invalidar toda a sessão em seu sistema, basta alterar a chave única global.
Tommy Aria Pradana 03/04
1

Fiz da seguinte maneira:

  1. Gere um unique hashe, em seguida, armazene-o em redis e em seu JWT . Isso pode ser chamado de sessão
    • Também armazenaremos o número de solicitações que o JWT específico fez - Cada vez que um jwt é enviado ao servidor, incrementamos o número inteiro de solicitações . (isso é opcional)

Portanto, quando um usuário efetua login, um hash exclusivo é criado, armazenado em redis e injetado no seu JWT .

Quando um usuário tenta visitar um ponto de extremidade protegido, você pega o hash de sessão exclusivo do seu JWT , consulta os redis e verifica se é uma correspondência!

Podemos estender isso e tornar nosso JWT ainda mais seguro, eis como:

Todo X solicita que um JWT específico tenha feito, geramos uma nova sessão exclusiva, armazenamos em nosso JWT e, em seguida , colocamos na lista negra a anterior.

Isso significa que o JWT está mudando constantemente e impede que o antigo JWT seja invadido, roubado ou qualquer outra coisa.

James111
fonte
1
Você pode fazer o hash do token em si e armazenar esse valor em redis, em vez de injetar um novo hash no token.
Frug
Além disso, verifique aude jtisolicite no JWT que você está no caminho certo.
Peter Lada
1

Se você deseja revogar tokens de usuário, pode acompanhar todos os tokens emitidos no seu banco de dados e verificar se eles são válidos (existem) em uma tabela do tipo sessão. A desvantagem é que você atingirá o banco de dados em todas as solicitações.

Eu não tentei, mas sugiro o método a seguir para permitir a revogação de token, mantendo o mínimo de acessos ao banco de dados -

Para diminuir a taxa de verificação do banco de dados, divida todos os tokens JWT emitidos em grupos X de acordo com alguma associação determinística (por exemplo, 10 grupos pelo primeiro dígito do ID do usuário).

Cada token JWT manterá o ID do grupo e um carimbo de data e hora criado na criação do token. por exemplo,{ "group_id": 1, "timestamp": 1551861473716 }

O servidor manterá todos os IDs de grupo na memória e cada grupo terá um registro de data e hora que indica quando foi o último evento de logoff de um usuário pertencente a esse grupo. por exemplo,{ "group1": 1551861473714, "group2": 1551861487293, ... }

Solicitações com um token JWT que possuem um registro de data e hora de grupo mais antigo, serão verificadas quanto à validade (ocorrência do banco de dados) e, se válidas, um novo token JWT com um registro de data e hora novo será emitido para uso futuro do cliente. Se o registro de data e hora do grupo do token for mais recente, confiamos no JWT (sem ocorrência de banco de dados).

Assim -

  1. Só validamos um token JWT usando o banco de dados se o token tiver um carimbo de data / hora do grupo antigo, enquanto solicitações futuras não serão validadas até que alguém do grupo do usuário efetue logout.
  2. Usamos grupos para limitar o número de alterações no registro de data e hora (digamos que há um usuário entrando e saindo como se não houvesse amanhã - afetará apenas um número limitado de usuários em vez de todos)
  3. Limitamos o número de grupos para limitar a quantidade de carimbos de data / hora mantidos na memória
  4. A invalidação de um token é fácil - basta removê-lo da tabela de sessões e gerar um novo registro de data e hora para o grupo de usuários.
Arik
fonte
A mesma lista pode ser mantida na memória (aplicativo para c #) e eliminaria a necessidade de atingir o banco de dados para cada solicitação. A lista pode ser carregado a partir de db no arranque da aplicação
dvdmn
1

Se a opção "sair de todos os dispositivos" for aceitável (na maioria dos casos, é):

  • Adicione o campo da versão do token ao registro do usuário.
  • Adicione o valor neste campo às reivindicações armazenadas no JWT.
  • Incremente a versão sempre que o usuário fizer logout.
  • Ao validar o token, compare sua declaração de versão com a versão armazenada no registro do usuário e rejeite se não for a mesma.

De qualquer maneira, é necessária uma viagem de banco de dados para obter o registro do usuário na maioria dos casos, para que isso não adicione muita sobrecarga ao processo de validação. Ao contrário da manutenção de uma lista negra, em que a carga do banco de dados é significativa devido à necessidade de usar uma ligação ou uma chamada separada, limpe os registros antigos e assim por diante.

user2555515
fonte
0

Vou responder Se precisarmos fornecer logout de todos os dispositivos quando estivermos usando o JWT. Essa abordagem usará pesquisas de banco de dados para cada solicitação. Porque precisamos de um estado de segurança persistente, mesmo se houver uma falha no servidor. Na tabela de usuários, teremos duas colunas

  1. LastValidTime (padrão: hora de criação)
  2. Conectado (padrão: true)

Sempre que houver uma solicitação de logout do usuário, atualizaremos LastValidTime para o horário atual e Logon-In para false. Se houver uma solicitação de logon, não alteraremos LastValidTime, mas o Logon-In será definido como true.

Quando criamos o JWT, teremos o tempo de criação do JWT na carga útil. Quando autorizamos um serviço, verificamos 3 condições

  1. O JWT é válido
  2. O tempo de criação da carga útil do JWT é maior que o LastValidTime do Usuário
  3. O usuário está logado

Vamos ver um cenário prático.

O usuário X possui dois dispositivos A, B. Ele fez login no nosso servidor às 19:00, usando o dispositivo A e o dispositivo B. (digamos que o tempo de expiração da JWT seja de 12 horas). A e B têm JWT com createdTime: 19:00

Às 21:00, ele perdeu o dispositivo B. Ele saiu imediatamente do dispositivo A. Isso significa que agora a entrada de usuário do banco de dados X tem LastValidTime como "ThatDate: 9: 00: xx: xxx" e o logon como "false".

Às 9:30, o Sr.Thief tenta efetuar login usando o dispositivo B. Verificaremos o banco de dados, mesmo que o Logon-In seja falso, portanto não permitiremos.

Às 22:00, o Sr.X efetua login no dispositivo A. Agora, o dispositivo A tem JWT com horário criado: 22:00. Agora o banco de dados conectado está definido como "true"

Às 22:30, o Sr. Thief tenta fazer login. Mesmo que o logon seja verdadeiro. O LastValidTime é 21:00 no banco de dados, mas o JWT de B criou a hora como 19:00. Portanto, ele não poderá acessar o serviço. Portanto, usando o dispositivo B sem ter a senha, ele não pode usar o JWT já criado após o logoff de um dispositivo.

Tharsanan
fonte
0

A solução do IAM, como o Keycloak (na qual trabalhei), fornece um ponto de extremidade de revogação de token, como

Ponto final de revogação de token /realms/{realm-name}/protocol/openid-connect/revoke

Se você quiser simplesmente desconectar um agente de usuário (ou usuário), também poderá chamar um ponto de extremidade (isso simplesmente invalidaria os Tokens). Novamente, no caso de Keycloak, o Terceiro de Confiança só precisa chamar o terminal

/realms/{realm-name}/protocol/openid-connect/logout

Link no caso, se você quiser saber mais

Subbu Mahadevan
fonte
-1

Isso parece realmente difícil de resolver sem uma consulta ao banco de dados em todas as verificações de token. A alternativa em que posso pensar é manter uma lista negra de tokens inválidos no lado do servidor; que deve ser atualizado em um banco de dados sempre que uma alteração persistir durante as reinicializações, fazendo o servidor verificar o banco de dados ao reiniciar para carregar a lista negra atual.

Mas se você o mantiver na memória do servidor (uma variável global de tipos), não será escalável em vários servidores se você estiver usando mais de um, portanto, nesse caso, você pode mantê-lo em um cache Redis compartilhado, que deve ser configuração para manter os dados em algum lugar (banco de dados? sistema de arquivos?), caso precise ser reiniciado, e toda vez que um novo servidor é ativado, é necessário assinar o cache do Redis.

Alternativa a uma lista negra, usando a mesma solução, você pode fazê-lo com um hash salvo em redis por sessão, como aponta essa outra resposta (sem ter certeza de que seria mais eficiente com muitos usuários que efetuam login).

Isso soa muito complicado? faz comigo!

Disclaimer: Eu não usei Redis.

Jose PV
fonte
-1

Se você estiver usando axios ou uma lib de solicitação de http baseada em promessa semelhante, poderá simplesmente destruir o token no front-end dentro da .then()peça. Ele será iniciado na parte resposta .then () após o usuário executar esta função (o código de resultado do terminal do servidor deve estar ok, 200). Depois que o usuário clica nessa rota durante a pesquisa de dados, se o campo do banco de dados user_enabledfor falso, o token de destruição será acionado e o usuário será desconectado imediatamente e impedido de acessar rotas / páginas protegidas. Não precisamos esperar que o token expire enquanto o usuário estiver permanentemente conectado.

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}
Dan
fonte
-3

Acabei de salvar o token na tabela users, quando o login do usuário atualizarei um novo token e quando auth for igual ao jwt atual do usuário.

Eu acho que essa não é a melhor solução, mas funciona para mim.

Vo Manh Kien
fonte
2
Claro que não é o melhor! Qualquer pessoa que tenha acesso ao banco de dados pode facilmente representar qualquer usuário.
user2555515
1
@ user2555515 Esta solução funciona bem se o token armazenado no banco de dados estiver criptografado, assim como qualquer senha armazenada no banco de dados. Há uma diferença entre Stateless JWTe Stateful JWT(que é muito semelhante às sessões). Stateful JWTpode se beneficiar da manutenção de uma lista de permissões de tokens.
TheDarkIn1978 28/07/19