Após atualizarmos nossos ambientes Discourse auto-hospedados em 11 de setembro de 2026, o login via OIDC começou a falhar com a seguinte mensagem:
(oidc) Authentication failure! jwt_decode_failed:
JWT::DecodeError, Nil JSON web token
Nosso ambiente de desenvolvimento mais antigo continuou funcionando.
Ambiente
- Discourse executado em Docker em uma VM Azure Linux.
- Azure Front Door → balanceador de carga interno → VM.
- Discourse hospedado sob
/forum. - Políticas personalizadas do Azure AD B2C para OIDC.
- Tráfego de saída da VM roteado através do Azure Firewall.
- Revisão do Discourse com falha:
74f839fb7. - Revisão de desenvolvimento funcional:
502aa3687.
As versões relevantes das dependências eram:
| Dependência | Dev funcional | Produção com falha |
|---|---|---|
| oauth2 | 1.4.11 | 2.0.25 |
| omniauth-oauth2 | 1.7.3 | 1.9.0 |
| jwt | 2.10.1 | 3.2.0 |
O repositório atualizado inclui d881bf2d4aabcf2430863a96beafaeabaf915702, “DEPS: Upgrade oauth2 to 2.x” (#43523).
Reprodução mínima
Isso não requer conexão de rede, infraestrutura Azure ou credenciais reais:
require "oauth2"
puts Gem.loaded_specs.fetch("oauth2").version
client = OAuth2::Client.new(
"dummy",
"dummy",
site: "https://example.invalid"
)
token = OAuth2::AccessToken.from_hash(
client,
{"id_token" => "dummy-id"}
)
puts "Main token: #{token.token.inspect}"
puts "ID token parameter: #{token["id_token"].inspect}"
Com OAuth2 1.4.11, o token principal fica vazio e o parâmetro do ID token permanece como "dummy-id".
Com OAuth2 2.0.25, o token principal se torna "dummy-id" e o parâmetro do ID token se torna nil.
Relação com a falha no Discourse
No ramo no-userinfo, o plugin OIDC constrói o token usando:
::OAuth2::AccessToken.from_hash(client, response.parsed)
Mais tarde, ele tenta:
::JWT.decode(access_token["id_token"], nil, false).first
Quando a biblioteca consome id_token como o token principal, a consulta subsequente do parâmetro retorna nil.
Solução alternativa testada
Alteramos a construção do token para preservar o ID token da resposta original:
payload = response.parsed
token = ::OAuth2::AccessToken.from_hash(client, payload)
token.params["id_token"] = payload["id_token"] if payload["id_token"]
token
Após aplicar esse patch e reiniciar o contêiner de UAT, o login foi bem-sucedido. Não foram necessárias alterações no Front Door, firewall ou B2C para essa recuperação.
Isso preserva a validação existente de claims e nonce e deixa o ramo userinfo inalterado.
A resposta bruta do token ao vivo não está incluída; o comportamento da biblioteca foi reproduzido com dados fictícios, e a solução alternativa foi validada por meio de um login real em UAT.
Isso já está coberto por uma correção upstream? Caso contrário, posso enviar um PR focado com cobertura de regressão.