.NET Core 2.2: Confiando em múltiplas assinaturas para validação de JWT
O Framework .Net Core possui uma estrutura bastante simplificada para tratamentos relacionados ao JWT (Criação/Validação). Se trata, basicamente, de configurar bibliotecas existentes que já contém toda a lógica necessária. Dentre essas configurações, informamos qual o “segredo” para validação da assinatura.
Essa forma de trabalho, apesar de abstrair grande parte da dor de cabeça que teríamos ao fazer tudo manualmente, acaba trazendo duas questões:
- O que acontece se quero validar mais de um segredo? Permitindo, assim, que JWTs gerados por diferentes IDPs (Identity Providers) sejam aceitos pela aplicação.
- O que acontece se aparentemente configurei tudo certo, mas o comportamento não reflete o esperado?
Compartilho aqui algumas experiências referentes a essas duas questões, através de atuação com multi-tenant, e como lidar com problemas.
Esse artigo não visa iniciar o uso de JWT. Para materiais introdutórios, seguem sugestões para estudo:
Também não são cobertas questões sobre a geração do token. O foco está na validação.
Contexto
Este foi o requisito recebido para um projeto em que estava atuando:
A Web API deve realizar autorização utilizando JWT, permitindo as requisições recebidas a partir do WSO2 API Manager
Apesar de parecer simples, demandou certos pontos de atenção:
- O gerador de JWT, para esse requisito, não é a minha aplicação, e sim o WSO2 Identity Server
- O algoritmo de encriptação utilizado pelo WSO2 Identity Server é “RS256”, e isso exige o uso de chave pública registrada no servidor. Essa pode ser obtida exportando certificado ou através do endpoint JWKS
- Cada tenant possui sua própria chave, como se fossem clients diferentes. Ou seja, a aplicação não teria que validar apenas uma origem, e sim N origens.
Solução
O código abaixo encontra-se no arquivo Startup.cs, onde está registrada a solução final para o problema.
A primeira coisa a ser feita é carregar os secrets a partir dos certificados. No meu caso, estes estavam em pasta local, mas também é possível importar a partir de certificate manager.
A partir da linha 26, foram adicionadas duas validações de JWT Token, uma para tokens gerados internamente através dessa aplicação, e outra qualquer requisição provinda do WSO2 (Desde que o certificado do tenant de origem esteja presente no servidor).
Essa foi a parte que colocou diversas possibilidades de assinaturas válidas para Token, ou seja, múltiplas origens/tokens:
paramsValidation.IssuerSigningKeyResolver = (t, st, k, p) => {
return certList;
};
Importando o segredo de certificados a partir de pasta local:
Idealmente, as informações acima são o suficiente para resolver o seu cenário, se for um requisito similar ao apresentado. Aí já pode terminar sua leitura por aqui. Obrigado!
Se você for azarado, assim como eu fui, e as coisas não acontecerem como deveriam, continue scrollando…
Não validou o JWT! E agora?
Quando registramos as regras de validação de Token, da maneira exemplificada acima, na prática estamos ativando um middleware de autenticação default do .NET Core. Devemos utilizar dessa forma em produção, visto que criar middlewares de autenticação manualmente não é recomendado pela Microsoft.
O problema que encontrei nessa abordagem foi o seguinte: Um JWT que é válido está retornando 401!
Entrei em jwt.io, tentei validar o JWT com o certificado, e sucesso, nenhum problema. Por que, então, minha aplicação não valida? O retorno não diz nada além de 401, o que está correto, visto que quanto mais informações fornecemos do motivo de uma falha de autenticação não ter sucesso, maior a chance de brechas de segurança. Entretanto, isso atrapalha para debug e entendimento de problemas.
A saída que encontrei foi, antes de voltar para a solução apresentada acima, desenvolver o meu próprio filtro de autorização manualmente, para conseguir identificar o erro em try/catch.
Deixe o startup.cs sem os registros de addJwtToken definidos acima! Não queremos o middleware padrão se metendo aqui. Vamos registrar esse atributo diretamente na controller.
Agora sim, descobriremos qual é o erro! Vai entrar no Catch. A partir desse momento, vai ficar muito mais fácil entender por onde atuar para resolver essa questão.
No meu caso, o erro era…
IDX10508: Signature validation failed. Signature is improperly formatted.
A correção para isso foi um tanto quanto esquisita:
dotnet add package Newtonsoft.Json --version 12.0.3
E foi assim que tudo voltou a funcionar: Instalando o Newtonsoft.
A classe JwtSecurityTokenHandler não funciona corretamente se o projeto não possuir o Newtonsoft.Json como dependência.
Resolvido isso, joguei fora o filtro customizado, devolvi as definições para o “startup.cs”, e tudo lindo!
Bônus: Capturando claims do JWT em outras camadas de controller e filtro
Pode ser que o seu requisito exija que, em uma outra camada posterior, ainda seja necessária a leitura de algum claim do JWT, por exemplo: Se meu issuer for A, faço algo. Se for B, faça outra coisa… para isso, preciso ler o valor de “iss”
Em uma controller:
Em um filtro:
Conclusão
O .Net Core possui por padrão um middleware responsável pela validação de tokens, configurado a partir do arquivo startup.cs. Basta utilizar a propriedade “IssuerSigningKeyResolver”, retornando uma lista de todos os segredos válidos para trabalhar com multi tenant.
Sempre procure utilizar esse filtro de autenticação padrão para ambientes em produção, visto que ele é mantido e garantido pela Microsoft. Utilize filtros de autenticação customizados apenas para facilitar a depuração de problemas, que são mascarados pelo padrão.