Desafio 9: tokens opacos de acesso

Desenhe um solução de autenticação e autorização em que seja possível usar tokens opacos de acesso em mais de um serviço. A emissão, invalidação e expiração do token precisa ser centralizada.

Esse é um problema que precisa de contextualização adequada. O escopo geral do problema resume-se a vários serviços protegidos acessando um único serviço de autenticação e autorização. Em uma linguagem visual temos o seguinte escopo:

O principal ponto que precisa ser esclarecido é a diferença entre token opaco e um JSON Web Token (JWT).

Token opaco: consiste em uma estrutura em que os dados sensíveis estão criptografados durante todo o processo de transmissão e uso do token. O responsável por descriptografar e validar o conteúdo acaba sendo o sistema emissor. O trade-off de performance acontece devido a seu tamanho: apesar de ser mais leve que um JWT, há a necessidade de validação em um único local (geralmente, um serviço de autorização). Nessa estrutura, pode gerar gargalos no ecossistema caso o serviço de autorização não seja dimensionado corretamente. Além disso, não existe um padrão determinado da estrutura de dados, podendo ser customizada conforme necessidade. Abaixo está um exemplo de uma estrutura opaca, que contém os dados criptografados que identificam o usuário, o tipo de token e a quantidade em segundos que ele expira:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNjIzMjQwMzg3LCJleHAiOjE2MjMyNDM5ODd9.-N5ohr6FC-e-b13n0ISATs3B7Yv1hKpr7KTENyNZ5UQ",
"token_type": "bearer",
"expires_in": 3600
}

JSON Web Token: É uma estrutura de dados autocontida que contém informações legíveis em formato JSON. Ele consiste em três partes: cabeçalho (que identifica o algoritmo de criptografia usado), payload (que identifica o que o usuário quer acessar, também chamado de claims) e uma assinatura opcional (que é usada para validar o conteúdo). O trade-off de performance é que este método não necessita de uma servidor centralizado de validação, porém, expõe dados potencialmente sensíveis no próprio token. Segue um exemplo:

{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1674316800
},
"signature": "HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secretKey)"
}

Independente do formato usado, as mesmas recomendações de segurança permanecem: não usem algoritmos fracos como MD5, mantenha as bases de dados usados na validação e emissão seguras, diminua a superfície potencial de ataque com técnicas como Zero Trust e afins.

Com essa contextualização feita, a solução em termos de arquitetura é simples. Basicamente consiste em serviço centralizado que possui uma persistência qualquer que irá armazenar o estado dos tokens emitidos. E uma api com três endpoints: um de geração (/auth), um de renovação (/renew) e um de revogação (/revoke). Em termos visuais, temos a seguinte estrutura:

exemplo da solução completa

Agora vamos analisar separadaramente cada um dos endpoints:

/auth: A entrada desse processo são as credenciais do usuário. O servidor verifica essas credenciais e, se estiverem corretas, gera dois tokens: um de acesso e um de atualização. O token de acesso é enviado de volta ao usuário, que pode utilizá-lo para acessar os serviços protegidos. Essa estrutura de dados contém informações sobre o usuário autenticado e suas permissões, garantindo que ele tenha acesso apenas aos recursos apropriados. É importante ressaltar que os dados de acesso não devem ser decodificados pelo cliente, protegendo assim as informações contidas nele. Enquanto isso, o token de atualização é armazenado no servidor para uso futuro. Isso permite que o servidor renove o token de acesso quando necessário, sem a necessidade de autenticação repetida do usuário, o que é uma ótima maneira de melhorar o desempenho do serviço e a experiência do usuário.

/renew: A entrada é o token de atualização retornado pela rota anterior. Se ele for validado corretamente, o servidor gera um novo token de acesso e um novo token de atualização. A estrutura de dados de acesso é retornada ao usuário, permitindo que ele continue acessando os serviços protegidos sem a necessidade de autenticação completa novamente. Ao mesmo tempo, o novo token de atualização é armazenado pelo servidor para uso futuro na renovação da autorização. Esse processo garante que o usuário tenha acesso contínuo aos serviços protegidos, mesmo após a expiração do token de acesso inicial. O uso de uma estrutura adicional de atualização permite que a autenticação seja mantida de forma transparente, sem interromper a experiência do usuário.

É possível simplificar essas rotas absorvendo essas funcionalidades da renovação na rota de emissão, através do uso de técnicas como idempotência no código para evitar problemas. No entanto, optei por deixar as rotas separadas para fins didáticos.

/revoke: por fim, essa rota simplesmente invalida os tokens de um determinado usuário (por exemplo, devido a um logout ou algum problema de segurança).

Com essa solução, é possível usar esse serviço para validação centralizada sem expor dados sensíveis pelo token. Esse serviço pode ser usado tanto em integrações de múltiplos serviços quanto em estruturas mais robustas, como o uso de API gateways (como explorado aqui anteriormente).

Caso você encontre algum erro ou informação incompleta neste artigo, por favor, me mande uma mensagem que eu ajustarei o texto.

Obrigado por ler até aqui!

Até!

Você gostou do conteúdo e gostaria de fazer mentoria comigo? Clique aqui e descubra como.

--

--