Falha no login OIDC com respostas apenas de ID-token após atualização para OAuth2 2.x

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.

1 Curtiu

Isso deveria ter sido corrigido por:

Por favor, nos avise se você ainda estiver vendo problemas após a implantação desse commit.

7 Curtiram

Obrigado. Isso resolve o problema.

1 Curtiu