instructure/canvas-lms · error · OAuthValidationError
Invalid JWT audience
Error message
Invalid JWT audience: %{audience} What it means
Raised in claims() when the ID token's aud (audience) claim does not include this provider's configured client_id. Canvas requires that the ID token was issued specifically for its OAuth client; otherwise the token may belong to another app and is rejected as an audience mismatch.
Solutions
- Confirm the client_id configured on the Canvas OpenID Connect provider exactly matches the aud claim in the rejected token.
- Re-issue credentials on the IdP and update client_id/client_secret in Canvas, then retry login.
- On the IdP, ensure the token is issued to the correct application (check azp/authorized party settings).
- Decode the failing ID token and compare aud against the provider settings before changing anything.
Example fix
# before (Canvas provider settings) client_id: "old-client-id" # after client_id: "the-client-id-matching-aud-claim"
Defensive patterns
Strategy: validation
Validate before calling
payload = JSON.parse(Base64.urlsafe_decode64(id_token.split(".")[1]))
aud = Array(payload["aud"])
raise "aud mismatch: #{payload['aud'].inspect} vs #{CLIENT_ID}" unless aud.include?(CLIENT_ID) Try / catch
begin
provider.claims(token)
rescue OAuthValidationError => e
Rails.logger.error("OIDC audience validation failed: #{e.message}")
head :unauthorized
end Prevention
- Keep client_id in Canvas and the IdP app registration in sync
- Regenerate both sides together when rotating credentials
- Compare aud in the error against the provider settings before changing anything
- Avoid whitespace when pasting client_id
When it happens
Trigger: claims(token) is invoked from unique_id/persist_to_session/provider_attributes; Array(id_token['aud']).include?(client_id) is false — the aud claim holds a different client ID, or the provider's client_id was changed/rotated after the token was issued.
Common situations: Admin regenerated OAuth credentials and the Canvas provider still has the old client_id (or vice versa); IdP issues tokens with azp/aud pointing to another registered app; copy-paste of client_id with whitespace or from the wrong app registration; multiple Canvas OpenID Connect providers sharing one IdP app.
Related errors
- Invalid JWT issuer
- Invalid nonce claim in ID Token
- 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/5f8a6a0326bff722.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/authentication_provider/open_id_connect.rb:283
retry
end
e.message
rescue JSON::JWT::VerificationFailed => e
e.message
end
def claims(token)
token.options[:claims] ||= begin
id_token = unverified_id_token(token)
unless (missing_claims = %w[aud iss iat exp nonce] - id_token.keys).empty?
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
View on GitHub (pinned to 1c9f0bb801)