Práticas recomendadas para tratamento do lado do servidor de tokens JWT [fechado]


111

(gerado a partir deste tópico, pois esta é realmente uma questão própria e não específica do NodeJS etc)

Estou implementando um servidor REST API com autenticação e implementei com êxito o manuseio de tokens JWT para que um usuário possa fazer login por meio de um ponto de extremidade / login com nome de usuário / senha, no qual um token JWT é gerado a partir de um segredo de servidor e retornado ao cliente. O token é então passado do cliente para o servidor em cada solicitação de API autenticada, na qual o segredo do servidor é usado para verificar o token.

No entanto, estou tentando entender as melhores práticas para saber exatamente como e em que medida o token deve ser validado, para fazer um sistema verdadeiramente seguro. O que exatamente deve estar envolvido na "validação" do token? É suficiente que a assinatura possa ser verificada usando o segredo do servidor ou devo também verificar o token e / ou carga útil do token em relação a alguns dados armazenados no servidor?

Um sistema de autenticação baseado em token só será tão seguro quanto passar nome de usuário / senha em cada solicitação, desde que seja tão ou mais difícil obter um token do que obter a senha de um usuário. No entanto, nos exemplos que vi, as únicas informações necessárias para produzir um token são o nome de usuário e o segredo do servidor. Isso não significa que, supondo por um minuto que um usuário malicioso obtenha conhecimento do segredo do servidor, ele agora pode produzir tokens em nome de qualquer usuário, tendo assim acesso não apenas a um determinado usuário, como aconteceria se uma senha fosse obtido, mas na verdade para todas as contas de usuário?

Isso me leva às perguntas:

1) A validação do token JWT deve se limitar a verificar a assinatura do próprio token, contando apenas com a integridade do segredo do servidor ou acompanhada por um mecanismo de validação separado?

  • Em alguns casos, tenho visto o uso combinado de tokens e sessões de servidor em que, após o login bem-sucedido por meio do endpoint / login, uma sessão é estabelecida. As solicitações de API validam o token e também comparam os dados decodificados encontrados no token com alguns dados armazenados na sessão. No entanto, usar sessões significa usar cookies e, de certa forma, isso vai contra o propósito de usar uma abordagem baseada em tokens. Também pode causar problemas para alguns clientes.

  • Pode-se imaginar o servidor mantendo todos os tokens atualmente em uso em um memcache ou semelhante, para garantir que, mesmo que o segredo do servidor seja comprometido para que um invasor possa produzir tokens "válidos", apenas os tokens exatos que foram gerados por meio do endpoint / login seria aceito. Isso é razoável ou apenas redundante / exagero?

2) Se a verificação da assinatura JWT for o único meio de validar tokens, ou seja, a integridade do segredo do servidor é o ponto de quebra, como os segredos do servidor devem ser gerenciados? Ler a partir de uma variável de ambiente e criar (aleatoriamente?) Uma vez por pilha implantada? Renovado ou girado periodicamente (e se sim, como lidar com tokens válidos existentes que foram criados antes da rotação, mas precisam ser validados após a rotação, talvez seja o suficiente se o servidor mantiver o segredo atual e o anterior a qualquer momento) ? Algo mais?

Talvez eu esteja simplesmente sendo paranóico demais quando se trata do risco de o segredo do servidor ser comprometido, o que é, obviamente, um problema mais geral que precisa ser resolvido em todas as situações criptográficas ...


1
Existem ótimas perguntas. Re: pergunta 2. Tenho o mesmo problema com QUALQUER chave secreta mantida do lado do servidor. Se você estiver executando qualquer tipo de correspondência de hash ou descriptografia assimétrica, - seja assinando um jwt ou descriptografando informações cc armazenadas no banco de dados, você precisa ter uma chave secreta acessível por código no servidor. Então, onde diabos você guarda isso ?? Aqui está a melhor resposta que encontrei: pcinetwork.org/forum/index.php?threads/… - provavelmente tão seguro quanto possível para uma chave jwt também.
jbd de

O que é a chave secreta no token jwt? Estou pensando que o próprio token é um segredo. Ou a chave secreta pode ser RSAPrivateKey privateKey??
kittu

3
Isso foi perguntado há algum tempo, mas talvez alguém ache útil. No meu caso, tenho uma "chave secreta" por usuário. Portanto, toda vez que um usuário faz login, eu gero esse segredo e armazeno o registro do usuário no banco de dados. Eu valido o token usando esse segredo. Após o logout, eu limpo esse valor. Isso invalida automaticamente outros tokens criados antes (isso é o que eu precisava).
Nelson Rodriguez

Respostas:


52

Também tenho usado tokens para meu aplicativo. Embora eu não seja um especialista de forma alguma, posso compartilhar algumas de minhas experiências e pensamentos sobre o assunto.

O objetivo dos JWTs é essencialmente integridade. Ele fornece um mecanismo para que seu servidor verifique se o token fornecido a ele é genuíno e foi fornecido por seu servidor. A assinatura gerada por meio do seu segredo é o que fornece isso. Então, sim, se o seu segredo vazar de alguma forma, esse indivíduo pode gerar tokens que o seu servidor pensaria serem seus. Um sistema baseado em token ainda seria mais seguro do que seu sistema de nome de usuário / senha simplesmente por causa da verificação de assinatura. E, neste caso, se alguém tiver seu segredo de qualquer maneira, seu sistema terá outros problemas de segurança para lidar além de alguém que está criando tokens falsos (e, mesmo assim, apenas alterar o segredo garante que todos os tokens feitos com o segredo antigo serão inválidos).

Quanto à carga útil, a assinatura só dirá que o token fornecido a você estava exatamente como estava quando o servidor o enviou. verificar se os conteúdos das cargas úteis são válidos ou apropriados para o seu aplicativo depende obviamente de você.

Para suas perguntas:

1.) Na minha experiência limitada, é definitivamente melhor verificar seus tokens com um segundo sistema. A simples validação da assinatura significa apenas que o token foi gerado com o seu segredo. Armazenar quaisquer tokens criados em algum tipo de banco de dados (redis, memcache / sql / mongo ou algum outro armazenamento) é uma maneira fantástica de garantir que você aceite apenas tokens criados por seu servidor. Nesse cenário, mesmo se seu segredo vazar, não terá muita importância, pois os tokens gerados não serão válidos de qualquer maneira. Esta é a abordagem que estou adotando com meu sistema - todos os tokens gerados são armazenados em um banco de dados (redis) e, em cada solicitação, verifico se o token está em meu banco de dados antes de aceitá-lo. Desta forma, os tokens podem ser revogados por qualquer motivo, como tokens que foram liberados de alguma forma, logout do usuário, alterações de senha, alterações de segredo, etc.

2.) Não tenho muita experiência e ainda estou pesquisando ativamente, pois não sou um profissional de segurança. Se você encontrar algum recurso, fique à vontade para publicá-lo aqui! Atualmente, estou usando apenas uma chave privada que carrego do disco, mas obviamente essa está longe de ser a melhor ou a mais segura solução.


5
Para o segundo ponto, aqui está uma boa resposta: security.stackexchange.com/questions/87130/…
Bossliaw

1
Como os tokens estão disponíveis no cabeçalho, e se o token for roubado e um mal-intencionado tentar fazer login com esse token (sabendo o endereço de e-mail do usuário)?
kittu de

22
Se você armazenar cada JWT, não haverá nenhum benefício para o JWT e você também pode ficar com ids de sessão aleatórios.
ColinM

46

Aqui estão algumas coisas a serem consideradas ao implementar JWTs em seu aplicativo:

  • Mantenha o tempo de vida do JWT relativamente curto e faça com que ele seja gerenciado no servidor. Se você não fizer isso, e posteriormente precisar exigir mais informações em seus JWTs, você terá que oferecer suporte a 2 versões ou esperar até que seus JWTs mais antigos tenham expirado antes de implementar sua alteração. Você pode gerenciá-lo facilmente no servidor se olhar apenas para o iatcampo no jwt e ignorar o expcampo.

  • Considere incluir o url da solicitação em seu JWT. Por exemplo, se você deseja que seu JWT seja usado no ponto de extremidade /my/test/path, inclua um campo como 'url':'/my/test/path'em seu JWT, para garantir que ele só seja usado neste caminho. Do contrário, você pode descobrir que as pessoas começam a usar seus JWTs em outros terminais, mesmo aqueles para os quais não foram criados. Você também pode considerar a inclusão de um md5 (url), pois ter um grande url no JWT fará com que o JWT seja muito maior e eles podem ficar bem grandes.

  • A expiração do JWT deve ser configurável para cada caso de uso se os JWTs estiverem sendo implementados em uma API. Por exemplo, se você tiver 10 terminais para 10 casos de uso diferentes para JWTs, certifique-se de fazer com que cada terminal aceite JWTs que expiram em momentos diferentes. Isso permite que você bloqueie alguns terminais mais do que outros, se, por exemplo, os dados servidos por um terminal são muito confidenciais.

  • Em vez de simplesmente expirar JWTs após um certo tempo, considere implementar JWTs que suportem ambos:

    • N usos - só pode ser usado N vezes antes de expirar e
    • expira após um determinado período de tempo (se você tiver um token de uso único, não o quer viver para sempre se não for usado, não é?)
  • Todas as falhas de autenticação JWT devem gerar um cabeçalho de resposta de "erro" que indica por que a autenticação JWT falhou. por exemplo, "expirado", "sem usos restantes", "revogado" etc. Isso ajuda os implementadores a saber por que o JWT está falhando.

  • Considere ignorar o "cabeçalho" de seus JWTs conforme eles vazam informações e fornecem uma medida de controle para os hackers. Isso se refere principalmente ao algcampo no cabeçalho - ignore isso e apenas suponha que o cabeçalho é o que você deseja oferecer suporte, pois isso evita que hackers tentem usar o Nonealgoritmo, o que remove a verificação de segurança da assinatura.

  • Os JWTs devem incluir um identificador detalhando qual aplicativo gerou o token. Por exemplo, se seus JWTs estão sendo criados por 2 clientes diferentes, mychat e myclassifiedsapp, cada um deve incluir o nome do projeto ou algo semelhante no campo "iss" no JWT, por exemplo, "iss": "mychat"

  • Os JWTs não devem ser registrados em arquivos de log. O conteúdo de um JWT pode ser registrado, mas não o próprio JWT. Isso garante que os desenvolvedores ou outros não possam obter os JWTs dos arquivos de log e fazer coisas nas contas de outros usuários.
  • Certifique-se de que sua implementação JWT não permite o algoritmo "Nenhum", para evitar que hackers criem tokens sem assiná-los. Essa classe de erros pode ser evitada totalmente ignorando o "cabeçalho" do seu JWT.
  • Considere fortemente o uso de iat(emitido em) em vez de exp(expiração) em seus JWTs. Por quê? Como iatbasicamente significa quando o JWT foi criado, isso permite que você ajuste no servidor quando o JWT expirar, com base na data de criação. Se alguém morrer em exp20 anos no futuro, o JWT basicamente viverá para sempre! Observe que você expira automaticamente os JWTs se eles iatestiverem no futuro, mas permita um pouco de espaço de manobra (por exemplo, 10 segundos), caso o horário do cliente esteja ligeiramente fora de sincronia com o horário do servidor.
  • Considere a implementação de um ponto de extremidade para criar JWTs a partir de uma carga útil json e force todos os seus clientes de implementação a usar esse ponto de extremidade para criar seus JWTs. Isso garante que você possa resolver quaisquer problemas de segurança que desejar com a forma como os JWTs são criados em um só lugar, facilmente. Não fizemos isso diretamente em nosso aplicativo e agora temos que liberar lentamente as atualizações de segurança do lado do servidor JWT porque nossos 5 clientes diferentes precisam de tempo para implementar. Além disso, faça seu endpoint de criação aceitar uma matriz de cargas úteis json para os JWTs criarem, e isso diminuirá o número de solicitações http que chegam a este endpoint para seus clientes.
  • Se o seu JWT for usado em terminais que também suportam o uso por sessão, certifique-se de não colocar nada em seu JWT que seja necessário para atender à solicitação. Você pode fazer isso facilmente se garantir que seu endpoint funcione com uma sessão, quando nenhum JWT é fornecido.
  • Portanto, o JWT, de modo geral, acaba contendo um userId ou groupId de algum tipo e permite o acesso a parte do seu sistema com base nessas informações. Certifique-se de não permitir que os usuários em uma área do seu aplicativo se façam passar por outros usuários, especialmente se isso fornecer acesso a dados confidenciais. Por quê? Bem, mesmo que seu processo de geração de JWT só seja acessível para serviços "internos", desenvolvedores ou outras equipes internas podem gerar JWTs para acessar dados de qualquer usuário, por exemplo, o CEO de alguma empresa de cliente aleatório. Por exemplo, se seu aplicativo fornece acesso a registros financeiros para clientes, ao gerar um JWT, um desenvolvedor pode obter os registros financeiros de qualquer empresa! E se um hacker entrar em sua rede interna de qualquer maneira, ele pode fazer o mesmo.
  • Se você pretende permitir que qualquer url que contenha um JWT seja armazenado em cache de alguma forma, certifique-se de que as permissões para diferentes usuários sejam incluídas no url, e não no JWT. Por quê? Porque os usuários podem acabar obtendo dados que não deveriam. Por exemplo, digamos que um superusuário faça login em seu aplicativo e solicite o seguinte url: /mysite/userInfo?jwt=XXXe que esse url seja armazenado em cache. Eles se desconectam e, alguns minutos depois, um usuário regular faz login em seu aplicativo. Eles obterão o conteúdo em cache - com informações sobre um superusuário! Isso tende a acontecer menos no cliente e mais no servidor, especialmente nos casos em que você está usando um CDN como o Akamai e está permitindo que alguns arquivos durem mais. Isso pode ser corrigido incluindo as informações relevantes do usuário na url e validando-as no servidor, mesmo para solicitações em cache, por exemplo/mysite/userInfo?id=52&jwt=XXX
  • Se o seu jwt se destina a ser usado como um cookie de sessão e só deve funcionar na mesma máquina para a qual o jwt foi criado, você deve considerar a adição de um campo jti ao seu jwt. Este é basicamente um token CSRF, que garante que seu JWT não possa ser passado do navegador de um usuário para outro.

1
O que você created_bychama de, já existe uma reclamação para isso no JWT e é chamado iss(emissor).
Fred

sim, bom ponto - vou atualizar com isso ... obrigado!
Brad Parks,

8

Não acho que seja um especialista, mas gostaria de compartilhar algumas idéias sobre Jwt.

  • 1: Como Akshay disse, é melhor ter um segundo sistema para validar seu token.

    a .: A maneira como eu lido com isso: eu armazeno o hash gerado em um armazenamento de sessão com o tempo de expiração. Para validar um token, ele precisa ter sido emitido pelo servidor.

    b.:Há pelo menos uma coisa que deve ser verificada no método de assinatura usado. por exemplo :

    header :
    {
      "alg": "none",
      "typ": "JWT"
    }
    

Algumas bibliotecas que validam o JWT aceitariam este sem verificar o hash. Isso significa que, sem saber o sal usado para assinar o token, um hacker pode conceder a si mesmo alguns direitos. Sempre certifique-se de que isso não aconteça. https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/

c .: Usar um cookie com um Id de sessão não seria útil para validar seu token. Se alguém quiser sequestrar a sessão de um usuário lambda, basta usar um sniffer (por exemplo: wirehark). Este hacker teria as duas informações ao mesmo tempo.

  • 2: É o mesmo para todos os segredos. Sempre existe uma maneira de saber isso.

A forma como trato disso está ligada ao ponto 1.a. : Eu tenho um segredo misturado com uma variável aleatória. O segredo é único para cada token.

No entanto, estou tentando entender as melhores práticas para saber exatamente como e em que medida o token deve ser validado, para fazer um sistema verdadeiramente seguro.

Se você deseja a melhor segurança possível, não deve seguir cegamente as práticas recomendadas. A melhor maneira é entender o que você está fazendo (acho que está tudo bem quando vejo sua pergunta) e, em seguida, avaliar a segurança de que você precisa. E se o Mossad quiser ter acesso aos seus dados confidenciais, eles sempre encontrarão uma maneira. (Gosto desta postagem do blog: https://www.schneier.com/blog/archives/2015/08/mickens_on_secu.html )


Bom ponto para ter um segredo exclusivo para cada token, mas como você cria um segredo exclusivo a cada vez? Estou usando a biblioteca nimbus jwt
kittu

1
provavelmente use a senha hash do seu usuário.
momokjaaaaa de

1
"Se você não estiver fazendo as coisas da mesma maneira que as outras pessoas, será mais difícil para as pessoas encontrarem uma saída para a sua segurança." Isso soa como segurança através da obscuridade para mim. As melhores práticas são chamadas assim porque reduzem os riscos mais comuns de uma forma prática.
Mnebuerquo

@Mnebuerquo Concordo totalmente com você, o cara que escreveu isso não deve ser confiável ;-)
Deblaton Jean-Philippe

1
Ele está correto, porém, que não se deve seguir cegamente as melhores práticas. É bom entender por que as melhores práticas são consideradas as melhores . Em cada decisão de design de segurança, há uma troca entre segurança e usabilidade. Compreender o porquê significa que você pode tomar essas decisões de forma inteligente. (Continue seguindo as práticas recomendadas, porque seus usuários não o farão.)
Mnebuerquo

3

Muitas respostas boas aqui. Vou integrar algumas das respostas que considero mais relevantes e adicionar mais algumas sugestões.

1) A validação do token JWT deve se limitar a verificar a assinatura do próprio token, contando apenas com a integridade do segredo do servidor ou acompanhada por um mecanismo de validação separado?

Não, por motivos não relacionados ao comprometimento de um segredo de token. Cada vez que um usuário efetua login por meio de um nome de usuário e senha, o servidor de autorização deve armazenar o token que foi gerado ou metadados sobre o token que foi gerado. Pense nesses metadados como um registro de autorização. Um determinado par de usuário e aplicativo deve ter apenas um token válido, ou autorização, a qualquer momento. Metadados úteis são o id do usuário associado ao token de acesso, o id do aplicativo e a hora em que o token de acesso foi emitido (o que permite a revogação dos tokens de acesso existentes e a emissão de um novo token de acesso). Em cada solicitação de API, valide se o token contém os metadados adequados. Você precisa manter as informações sobre quando cada tokens de acesso foi emitido, para que um usuário possa revogar os tokens de acesso existentes se suas credenciais de conta forem comprometidas e fazer login novamente e começar a usar um novo token de acesso. Isso atualizará o banco de dados com a hora em que o token de acesso foi emitido (a hora de autorização criada). Em cada solicitação de API, verifique se o tempo de emissão do token de acesso é posterior ao tempo de autorização criado.

Outras medidas de segurança incluem não registrar JWTs e exigir um algoritmo de assinatura seguro como o SHA256.

2) Se a verificação da assinatura JWT for o único meio de validar tokens, ou seja, a integridade do segredo do servidor é o ponto de quebra, como os segredos do servidor devem ser gerenciados?

O comprometimento dos segredos do servidor permitiria a um invasor emitir tokens de acesso para qualquer usuário, e o armazenamento de dados do token de acesso na etapa 1 não impediria necessariamente o servidor de aceitar esses tokens de acesso. Por exemplo, digamos que um token de acesso tenha sido emitido para um usuário e, posteriormente, um invasor gere um token de acesso para esse usuário. O tempo de autorização do token de acesso seria válido.

Como Akshay Dhalwala diz, se seu segredo do lado do servidor for comprometido, você terá problemas maiores para lidar, porque isso significa que um invasor comprometeu sua rede interna, seu repositório de código-fonte ou ambos.

No entanto, um sistema para mitigar o dano de um segredo de servidor comprometido e evitar o armazenamento de segredos no código-fonte envolve a rotação de segredo de token usando um serviço de coordenação como https://zookeeper.apache.org. Use um cron job para gerar um segredo de aplicativo a cada poucas horas ou mais (por mais tempo que seus tokens de acesso sejam válidos) e envie o segredo atualizado para o Zookeeper. Em cada servidor de aplicativos que precisa saber o segredo do token, configure um cliente ZK que é atualizado sempre que o valor do nó ZK muda. Armazene um segredo primário e um secundário e, sempre que o segredo do token for alterado, defina o novo segredo do token para o primário e o segredo do token antigo para o secundário. Dessa forma, os tokens válidos existentes ainda serão válidos porque eles serão validados em relação ao segredo secundário. Quando o segredo secundário for substituído pelo segredo primário antigo, todos os tokens de acesso emitidos com o segredo secundário terão expirado de qualquer maneira.


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.