After updating our self-hosted Discourse environments on 11 September 2026, OIDC login started failing with:
(oidc) Authentication failure! jwt_decode_failed:
JWT::DecodeError, Nil JSON web token
Our older dev environment continued working.
Environment
- Discourse running in Docker on an Azure Linux VM.
- Azure Front Door → internal load balancer → VM.
- Discourse hosted under
/forum. - Azure AD B2C custom policies for OIDC.
- VM outbound traffic routed through Azure Firewall.
- Failing Discourse revision:
74f839fb7. - Working dev revision:
502aa3687.
The relevant dependency versions were:
| Dependency | Working dev | Failing production |
|---|---|---|
| oauth2 | 1.4.11 | 2.0.25 |
| omniauth-oauth2 | 1.7.3 | 1.9.0 |
| jwt | 2.10.1 | 3.2.0 |
The updated repository includes d881bf2d4aabcf2430863a96beafaeabaf915702, “DEPS: Upgrade oauth2 to 2.x” (#43523).
Minimal reproduction
This requires no network connection, Azure infrastructure or real credentials:
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}"
With OAuth2 1.4.11, the main token is empty and the ID token parameter remains "dummy-id".
With OAuth2 2.0.25, the main token becomes "dummy-id" and the ID token parameter becomes nil.
Connection to the Discourse failure
In the no-userinfo branch, the OIDC plugin constructs the token using:
::OAuth2::AccessToken.from_hash(client, response.parsed)
Later, it attempts:
::JWT.decode(access_token["id_token"], nil, false).first
When the library consumes id_token as the main token, the subsequent parameter lookup returns nil.
Tested workaround
We changed the token construction to preserve the ID token from the original response:
payload = response.parsed
token = ::OAuth2::AccessToken.from_hash(client, payload)
token.params["id_token"] = payload["id_token"] if payload["id_token"]
token
After applying this patch and restarting the UAT container, login succeeded. No Front Door, firewall or B2C changes were needed for that recovery.
This preserves the existing claim and nonce validation and leaves the userinfo branch unchanged.
The raw live token response is not included; the library behavior was reproduced with fake data, and the workaround was validated through a real UAT login.
Is this already covered by an upstream fix? Otherwise, I can submit a focused PR with regression coverage.