instructure/canvas-lms · error · OAuthValidationError
Invalid nonce claim in ID Token
Error message
Invalid nonce claim in ID Token
What it means
Raised in claims() when the ID token's nonce claim does not equal the nonce Canvas stored in the OAuth transaction (token.options[:nonce]). Nonce binds the ID token to this specific authorization request, preventing token replay/injection; a mismatch means the token may not correspond to this login attempt.
Solutions
- Verify the IdP echoes back the exact nonce sent in the authorization request (enable nonce support).
- Clear stale OAuth state and start a fresh login (avoid replayed/back-button callbacks).
- Ensure Canvas sessions are shared across all app nodes (Redis/shared session store) so token.options[:nonce] matches.
- Test the raw ID token to see what nonce the IdP actually returned and correct the request flow.
Example fix
# before: IdP configured without nonce support authorization_url without nonce param -> token nonce nil // after: enable OIDC nonce in IdP client settings so the returned ID token includes the request nonce
Defensive patterns
Strategy: validation
Validate before calling
payload = JSON.parse(Base64.urlsafe_decode64(id_token.split(".")[1]))
raise "nonce mismatch" unless payload["nonce"] == stored_nonce Try / catch
begin
provider.claims(token)
rescue OAuthValidationError => e
Rails.logger.warn("OIDC nonce mismatch: #{e.message}")
redirect_to login_path, alert: "Login session expired; please try again."
end Prevention
- Always send and verify nonce in authorization requests
- Use a shared session store across app nodes
- Discard stale OAuth state; never replay authorization codes
- Confirm IdP supports nonce before enabling the provider
When it happens
Trigger: claims(token) during unique_id/persist_to_session/provider_attributes finds id_token['nonce'] != token.options[:nonce] — e.g. the IdP echoes a different nonce, the request's nonce was regenerated, or the IdP does not support nonce and omits/alters it.
Common situations: Non-compliant IdP that ignores the nonce request parameter; back-button/replayed authorization reusing an old code with a stale token; load-balanced Canvas where session data (nonce) is not shared across nodes; clock/issue where the auth request was started before a provider settings change.
Related errors
- Invalid JWT audience
- Invalid JWT issuer
- Invalid signature
- Missing claims
- Failed to decode OpenID Connect back-channel logout token: #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/8b4811f12cbae8e1.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/authentication_provider/open_id_connect.rb:295
raise OAuthValidationError, t({ one: "Missing claim %{claims}", other: "Missing claims %{claims}" },
count: missing_claims.length,
claims: missing_claims.join(", "))
end
unless Array(id_token["aud"]).include?(client_id)
raise OAuthValidationError, t("Invalid JWT audience: %{audience}", audience: id_token["aud"].inspect)
end
if self.class.validate_issuer?
if issuer.blank?
raise OAuthValidationError, t("No issuer configured for OpenID Connect provider")
end
unless issuer === id_token["iss"] # rubocop:disable Style/CaseEquality -- may be a string or a RegEx
raise OAuthValidationError, t("Invalid JWT issuer: %{issuer}", issuer: id_token["iss"])
end
end
unless id_token["nonce"] == token.options[:nonce]
raise OAuthValidationError, t("Invalid nonce claim in ID Token")
end
if (signature_error = validate_signature(id_token))
raise OAuthValidationError, t("Invalid signature: %{signature_error}", signature_error:)
end
# we have a userinfo endpoint, and we don't have everything we want,
# then request more
if userinfo_endpoint.present? && !(requested_claims - id_token.keys).empty?
userinfo = token.get(userinfo_endpoint).parsed
debug_set(:userinfo, userinfo.to_json) if instance_debugging
# but only use it if it's for the user we logged in as
# see http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
if userinfo["sub"] == id_token["sub"]
id_token.merge!(userinfo)
end
end
id_tokenView on GitHub (pinned to 1c9f0bb801)