instructure/canvas-lms · error · OAuthValidationError

User is from unacceptable tenant

Error message

User is from unacceptable tenant %{tenant}.

What it means

Raised by the Microsoft authentication provider's unique_id when allowed_tenants is configured (non-empty, not 'common', skip_tenant_verification off) and the 'guests' branch is not taken, but the ID token's tid (tenant ID) claim is not in the allowed tenant list. The user authenticated with Microsoft but belongs to a tenant the Canvas admin did not allow.

Solutions

  1. Compare the tid in the error to the configured tenant list and add the missing tenant GUID.
  2. Use the directory's tenant GUID (Azure portal > Azure AD > Overview), not the domain name, in the allowed tenants config.
  3. Set skip_tenant_verification or include 'common' if all Microsoft users should be permitted.
  4. Ask the user to sign in with an account from an allowed tenant.

Example fix

# before
allowed_tenants: "contoso.onmicrosoft.com"
# after
allowed_tenants: "72f988bf-86f1-41af-91ab-2d7cd011db47"
Defensive patterns

Strategy: validation

Validate before calling

tid = id_token["tid"]
allowed = provider_settings["allowed_tenants"].split(",").map(&:strip)
raise "tenant #{tid} not allowed" unless allowed.empty? || allowed.include?("common") || allowed.include?(tid)

Try / catch

begin
  user.unique_id
rescue OAuthValidationError => e
  Rails.logger.warn("Microsoft tenant rejected: #{e.message}")
  render json: { error: "tenant_not_allowed" }, status: :forbidden
end

Prevention

When it happens

Trigger: unique_id runs during Microsoft OAuth callback with a fixed allow-list of tenants; id_token['tid'] (user's home tenant GUID) is not one of the configured allowed_tenants values, so the elsif fails and raises.

Common situations: User signs in with a personal or different-org Microsoft account; admin allow-list contains tenant domain names instead of tenant GUIDs (tid is a GUID); org migrated to a new tenant ID after rebranding/merger.

Related errors


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

Appendix: source

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

    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
    value << "microsoft" if value.delete(MICROSOFT_TENANT)
    value = ["common"] if value.include?("common")
    self["tenant"] = value.first

View on GitHub (pinned to 1c9f0bb801)