Dopo l’aggiornamento dei nostri ambienti Discourse self-hosted l’11 settembre 2026, l’accesso tramite OIDC ha iniziato a fallire con:
(oidc) Authentication failure! jwt_decode_failed:
JWT::DecodeError, Nil JSON web token
Il nostro ambiente di sviluppo più vecchio continuava a funzionare.
Ambiente
- Discourse eseguito in Docker su una VM Azure Linux.
- Azure Front Door → bilanciatore del carico interno → VM.
- Discourse ospitato sotto
/forum. - Policy personalizzate di Azure AD B2C per OIDC.
- Traffico in uscita dalla VM instradato tramite Azure Firewall.
- Revisione Discourse con errore:
74f839fb7. - Revisione di sviluppo funzionante:
502aa3687.
Le versioni rilevanti delle dipendenze erano:
| Dipendenza | Dev funzionante | Produzione con errore |
|---|---|---|
| oauth2 | 1.4.11 | 2.0.25 |
| omniauth-oauth2 | 1.7.3 | 1.9.0 |
| jwt | 2.10.1 | 3.2.0 |
Il repository aggiornato include d881bf2d4aabcf2430863a96beafaeabaf915702, “DEPS: Upgrade oauth2 to 2.x” (#43523).
Riproduzione minima
Questo non richiede connessione di rete, infrastruttura Azure o credenziali reali:
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}"
Con OAuth2 1.4.11, il token principale è vuoto e il parametro ID token rimane "dummy-id".
Con OAuth2 2.0.25, il token principale diventa "dummy-id" e il parametro ID token diventa nil.
Collegamento con il fallimento di Discourse
Nella branch senza userinfo, il plugin OIDC costruisce il token utilizzando:
::OAuth2::AccessToken.from_hash(client, response.parsed)
In seguito, tenta:
::JWT.decode(access_token["id_token"], nil, false).first
Quando la libreria consuma id_token come token principale, la successiva ricerca del parametro restituisce nil.
Workaround testato
Abbiamo modificato la costruzione del token per preservare l’ID token dalla risposta originale:
payload = response.parsed
token = ::OAuth2::AccessToken.from_hash(client, payload)
token.params["id_token"] = payload["id_token"] if payload["id_token"]
token
Dopo l’applicazione di questa patch e il riavvio del container UAT, l’accesso ha avuto successo. Non sono state necessarie modifiche a Front Door, firewall o B2C per quel recupero.
Questo preserva la convalida esistente di claim e nonce e lascia invariata la branch userinfo.
La risposta del token live grezzo non è inclusa; il comportamento della libreria è stato riprodotto con dati fittizi e il workaround è stato validato tramite un accesso UAT reale.
Questa è già coperta da una fix upstream? In caso contrario, posso inviare un PR mirato con copertura di regressione.