instructure/canvas-lms · error · OAuthValidationError

User is from unacceptable issuer

Error message

User is from unacceptable issuer %{issuer}.

What it means

Raised by Canvas's Microsoft authentication provider during OAuth login when the admin configured the provider to allow 'guests' tenants plus specific tenant IDs, but the ID token's issuer (iss) does not match https://login.microsoftonline.com/<tenant>/v2.0 for any allowed tenant. It is an OAuthValidationError meaning the user authenticates successfully with Microsoft but is not from an acceptable tenant.

Solutions

  1. Add the exact tenant ID(s) whose users should be allowed to the provider's allowed_tenants config so the issuer URL matches.
  2. Verify the configured tenant IDs match the 'tid' claim shown in the rejected issuer URL in the error message.
  3. If any Microsoft account should be allowed, set skip_tenant_verification (or include 'common') in the provider settings.
  4. Confirm the issuer version: the check expects the /v2.0 endpoint; ensure the provider uses the v2.0 endpoints.

Example fix

# before
settings["microsoft_tenant"] = "guests,contoso.onmicrosoft.com"
# after (use the tenant GUID or ensure issuer matches)
settings["allowed_tenants"] = "guests,00000000-0000-0000-0000-000000000000"
Defensive patterns

Strategy: validation

Validate before calling

iss = id_token["iss"]
allowed = provider_settings["allowed_tenants"].split(",")
unless allowed.empty? || allowed.include?("common") || provider_settings["skip_tenant_verification"]
  raise "unacceptable issuer #{iss}" if allowed.include?("guests") && !allowed.grep_v("guests").any? { |t| iss == "https://login.microsoftonline.com/#{t}/v2.0" }
end

Try / catch

begin
  user.unique_id
rescue OAuthValidationError => e
  Rails.logger.warn("Microsoft tenant rejected: #{e.message}")
  redirect_to login_path, alert: "Your Microsoft account is not from an allowed tenant."
end

Prevention

When it happens

Trigger: unique_id runs after Microsoft callback: allowed_tenants is non-empty, does not include 'common', skip_tenant_verification is off, and 'guests' was in the configured tenants list; id_token['iss'] (e.g. https://login.microsoftonline.com/9188040d-.../v2.0 for personal MSA accounts) matches none of the allowed tenant issuer URLs.

Common situations: Admin adds 'guests' to allowed tenants expecting guest accounts to pass, but the user's issuer is a tenant not explicitly listed (personal Microsoft accounts or guest home tenants). Azure AD tenant ID changes or issuer URL version differs (v2.0 vs v1.0). Typos in tenant IDs in the allowed list.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/d83e169ca7c0ff25. Report an issue: GitHub.

Appendix: source

Thrown at app/models/authentication_provider/microsoft.rb:103

  end

  def self.validate_issuer?
    false
  end

  def login_attribute
    raw_login_attribute || "tid+oid"
  end

  def unique_id(token)
    id_token = claims(token)
    allowed_tenants = mapped_allowed_tenants
    if allowed_tenants.empty? || allowed_tenants.include?("common") || settings["skip_tenant_verification"]
      # allow anyone
    elsif allowed_tenants.delete("guests")
      # just check the issuer
      unless allowed_tenants.find { |tenant| id_token["iss"] == "https://login.microsoftonline.com/#{tenant}/v2.0" }
        raise OAuthValidationError, t("User is from unacceptable issuer %{issuer}.", issuer: id_token["iss"].inspect)
      end
    elsif !allowed_tenants.include?(id_token["tid"])
      raise OAuthValidationError, t("User is from unacceptable tenant %{tenant}.", tenant: id_token["tid"].inspect)
    end

    ids = id_token.as_json
    ids["tid+oid"] = "#{ids["tid"]}##{ids["oid"]}" if ids["tid"] && ids["oid"]
    ids.slice("tid", *self.class.login_attributes)
  end

  # always process through the multi-valued setter
  def tenant=(value)
    self.tenants = value
  end

  def tenants=(value)
    value = value.split(",") if value.is_a?(String)
    value = value.filter_map(&:strip).uniq

View on GitHub (pinned to 1c9f0bb801)