Front End

20 ago, 2018

Implementando autenticação JWT utilizando React

Publicidade

Quando pensamos em sistemas distribuídos hoje em dia, uma das grandes questões que surgem é a relação entre sua aplicação e a autenticação do seu usuário. Antigamente, um modelo comum de autenticação era a utilização dos próprios recursos que a arquitetura REST nos provê, como o basic e o digest, mas, com a evolução das tecnologias e, consequentemente, dos perigos, viu-se necessário um modelo mais eficiente do que as sessões armazenadas em servidor e mais seguro do que a autenticação via Headers HTTP.

Por este motivo surgiu o que chamamos de JWT, o JSON Web Token. Neste artigo de três partes, vamos cobrir, primeiramente, o que é o JWT e como ele funciona. Nos demais vamos construir uma aplicação simples utilizando React para demonstrar como podemos utilizar este modelo de autenticação manualmente.

O que é JWT?

Como o próprio nome diz, JWT é um token de autenticação; isso significa que além de ser leve, ele também é o que eu costumo chamar de token auto-contido – você já vai entender o porquê. Baseado na RFC7519, ele é um padrão homologado pela W3C.

Ele pode ser utilizado para autenticar qualquer tipo de comunicação entre dois sistemas, sob qualquer protocolo (embora ele tenha sido muito usado utilizando HTTP).

A estrutura de um JWT basicamente se resume em três partes:

  • Header: contém a informação sobre o token em si
  • Payload: é, de fato, a informação útil para a aplicação
  • A assinatura, que é o que vamos utilizar para ter certeza de que o token é válido e não foi alterado

Um fluxo simples de autenticação via JWT é baseado neste diagrama:

Primeiramente temos que enviar nossas credenciais para um servidor que irá gerar este token e nos devolver.

Anatomia de um token

Vamos entender parte a parte como funciona um token JWT.

Header

O header, assim como todo o resto do token, é um objeto JSON, que contém duas informações principais: typ, que é sempre JWT, e o alg, que é o algoritmo utilizado para encriptar e decriptar a nossa assinatura. Para este último item, geralmente utilizamos o SHA256 (que é descrito como HS256) ou um algoritmo baseado em chaves assimétricas, ou RSA.

No geral, esta é a “cara” de um header JWT:

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

Vamos utilizar o HS256 aqui por um motivo simples: ele é mais fácil de reproduzir, já que não precisamos criar um caminho /.well-known/ para poder pegar essas chaves assimétricas.

Payload

Esta é a parte mais importante do token. Será aqui que colocaremos a informação do nosso usuário. Por isso que esse token é chamado de auto-contido, pois, além de validar que um usuário é autenticado, ele também contém a informação do próprio usuário. Isso é possível porque temos o que é chamado de public claims e reserved claims.

Os public claims são onde vamos colocar a informação do nosso usuário; aqui podemos escolher qualquer par chave/valor para colocar nossa informação. Já nos reserved claims, temos que obrigatoriamente incluir campos que são reservados (por isso o nome) para o JWT ser válido. Isso foi feito basicamente porque, como o token pode ser utilizado, literalmente, em qualquer lugar, se fez necessário a criação de um padrão para todas pudessem seguir.

Os reserved claims são:

  • iss: uma abreviação de issuer, que basicamente é o nome (ou a definição) da API ou do server que gerou o JWT. Esse campo é obrigatório porque provê uma identificação da origem deste token. Se quisermos mais segurança, podemos adicionar uma chave criptografada que pode ser lida pelo servidor que a validará como forma adicional de autenticação.
  • aud: Abreviação de audience, é utilizado para definir quem poderá ler (ou utilizar) o token. Juntamente com o iss, podemos restringir ainda mais as permissões para determinado token para uma certa “audiência”.
  • exp: Abreviação de expiration. Este é o atributo que vai nos dizer quando um token vai expirar. Isso é necessário porque os tokens são trafegados em texto. Se um atacante desejar apenas obter uma chave válida e o sistema que faz essa validação, geralmente por descuido de desenvolvimento, não tiver expiração. Então este atacante pode “roubar” a chave e utilizar ela indefinidamente como acesso para sua aplicação. Por isto, neste campo é colocado o timestamp da data de expiração.
  • sub: Abreviação de subject. A parte mais útil do token, aqui é onde vamos colocar o ID, login ou qualquer outra informação que identificará o usuário no seu sistema.

Um payload exemplo poderia ser:

{
    "iss": "http://meugerador.com.br",
    "exp": "1516239022",
    "iat": "1516209022",
    "sub": "iddousuario",
    "login": "lucas.santos",
    "name": "Lucas Santos"
}

Perceba que aqui temos parte dos nossos public claims, como nossos campos login e name. Mas também temos os reserved claims, iss, exp e sub, também temos um tipo de claim que não foi dito, que são os optional claims. Estes campos não são obrigatórios, mas agregam bastante informação. Neste exemplo, o campo iat é uma abreviação de issued at, que é a data de geração do token. Este campo será utilizado para comparar a data de expiração e verificar se o token está, de fato, válido.

Fazemos isso porque podemos mudar a hora do nosso relógio a qualquer momento, mas não podemos mudar a hora de outro computador remotamente de forma simples.

Assinatura

A assinatura será usada para verificar se o token não foi modificado no meio do caminho; o chamado ataque man in the middle, que é quando o invasor captura a requisição do usuário e a modifica, enviando uma nova requisição modificada com seus próprios dados, personificando o usuário.

A criptografia desta assinatura é reversível somente através de uma chave chamada secret. Se a pessoa não tiver esta chave, a assinatura não poderá ser decodificada. Isso resolve dois problemas; primeiramente o atacante não poderá ver as informações que o usuário enviou na assinatura, e também prevenimos que esta mesma pessoa altere os dados deste token, porque uma vez alterado, o hash final não será o mesmo do hash inicial geral, indicando um conflito e, consequentemente, transformando este token em um token inválido. Então garantimos a integridade de todas as informações.

Para gerar essa assinatura, o algoritmo de criptografia busca o header (que já falamos acima), o transforma em base64 e concatena com a base64 do payload, e então criptografa tudo com o secret que indicamos anteriormente.

Pondo em planos simples, esta seria a ideia:

HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secret
)

No final de tudo teremos um token válido com esta aparência:

Então devemos utilizar esse token no nosso cabeçalho Authorization da seguinte forma:

Authorization: Bearer <token>

Conclusão

Agora que já sabemos como funciona um JWT, no próximo artigo, vamos abordar como podemos utilizar este token para realizar um login seguro através de uma aplicação React, começando pelo lado do cliente. Se você quiser aprender um pouco mais sobre ele, dê uma olhada neste artigo e também no site do maior provedor da especificação.

Até mais!