.NET 6 の Negotiate 認証で System.DirectoryServices.Protocols の FileNotFoundException が発生する原因と解決方法

ASP.NET Core の Web アプリに Microsoft.AspNetCore.Authentication.Negotiate を追加した途端、起動時に System.IO.FileNotFoundException が出て先に進めない――。しかもローカル開発では動くのに、発行した本番環境だけこける…。本記事では、.NET 6/.NET 7 環境でよくハマる「System.DirectoryServices.Protocols が見つからない」問題の原因を整理しつつ、パッケージ構成の考え方から具体的な対処方法まで、実務目線で詳しく解説します。

目次

エラーの症状と再現パターン

問題となるケースでは、主に次のような構成になっていることが多いです。

  • ターゲットフレームワーク:.NET 6 (net6.0) または .NET 7 (net7.0) の ASP.NET Core Web アプリ
  • NuGet で Microsoft.AspNetCore.Authentication.Negotiate (6.0.14) を追加
  • Program.cs または Startup.cs に Negotiate 認証を設定

services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
        .AddNegotiate();

services.AddAuthorization(o => o.FallbackPolicy = o.DefaultPolicy);

この状態でアプリを起動すると、コンソールやイベントログに次のような例外が出ることがあります。


System.IO.FileNotFoundException:
Could not load file or assembly
'System.DirectoryServices.Protocols, Version=6.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.
The system cannot find the file specified.

よくある行動として、エラーメッセージに System.DirectoryServices.Protocols と出ているため、 「じゃあ System.DirectoryServices / System.DirectoryServices.Protocols の最新版 (7.0.0) を入れてみよう」 と考え、NuGet から追加します。しかし、それでもまったく解決せず、同じ FileNotFoundException に悩まされる…という流れになりがちです。

さらに、Visual Studio の「発行」プロファイルで 「Trim Unused Code」(未使用コードのトリミング) を有効にしていると、 開発環境では正常に動くのに「公開した環境だけ」このエラーが発生するパターンもあります。

Negotiate 認証と System.DirectoryServices.Protocols の関係

まず、このエラーがなぜ Microsoft.AspNetCore.Authentication.Negotiate と関係するのかを整理しておきます。

  • Microsoft.AspNetCore.Authentication.Negotiate は、ASP.NET Core における Windows 統合認証 (Kerberos / NTLM) を提供するパッケージです。
  • 多くの場合、Active Directory との連携が絡むため、バックエンドでは System.DirectoryServices.Protocols を経由して LDAP / Kerberos 関連の処理を行います。
  • そのため、Negotiate パッケージは内部的に System.DirectoryServices.Protocols に依存しており、適切なバージョンの DLL がアプリ出力に含まれている必要があります。

つまり、この FileNotFoundException は「Negotiate 認証自体が悪い」というより、 Negotiate が必要とするバージョンの System.DirectoryServices.Protocols が正しく解決されていないことが本質的な原因です。

原因:依存パッケージの“世代”が混ざっている

この問題の根っこにあるのは、NuGet パッケージの“世代”が混在していることです。 ここで言う「世代」とは、メジャーバージョン (6.x / 7.x / 8.x …) を指します。

ASP.NET Core / .NET の世界では、基本的に次のような対応関係を意識しておくとトラブルが減ります。

ターゲットフレームワーク推奨される NuGet パッケージ世代例
.NET 6 (net6.0)6.x 系Microsoft.AspNetCore.Authentication.Negotiate 6.x
System.DirectoryServices.* 6.x
.NET 7 (net7.0)7.x 系Microsoft.AspNetCore.Authentication.Negotiate 7.x
System.DirectoryServices.* 7.x
.NET 8 (net8.0)8.x 系同様に 8.x でそろえる

ところが、実際のプロジェクトでは次のようなアンチパターンがよく起こります。

  • アプリ本体は net6.0 をターゲットにしているのに、System.DirectoryServices.Protocols 7.x を手動で追加してしまう。
  • 社内共通ライブラリが System.DirectoryServices.Protocols 7.x に依存しており、Web アプリ側で Negotiate 6.x を追加してしまう。

こうして 6.x を要求するパッケージ (Negotiate 6.x) と、7.x を要求するパッケージ が同じプロセス内に同居すると、ビルド時・実行時のどこかで 「参照すべき DLL が無い」「期待したバージョンと違う」と判断され、結果として FileNotFoundException が表面化します。

対処法の全体像

まず先に、対処法をざっくり一覧で整理しておきます。

対処内容主な目的
① 依存パッケージの“世代”をそろえるアプリ全体で 6.x なら 6.x に、7.x なら 7.x に統一する根本原因であるバージョン競合を解消
② NuGet に任せて自動解決手動で追加した DirectoryServices 系を外し、Negotiate を入れ直す余計なパッケージを減らし、依存解決をシンプルにする
③ バージョン固定で衝突を回避.csproj で明示的に System.DirectoryServices.Protocols 6.0.1 などを指定する上流ライブラリから紛れ込む 7.x をブロック
④ 発行時のトリミングを無効化Publish プロファイルや .csproj で Trim をオフにする発行時だけ DLL が消える問題を防ぐ
⑤ その他チェックポイントdotnet clean / dotnet restore、bin フォルダの DLL 確認などキャッシュや環境差異による不具合を除外

以下では、それぞれの対処方法についてもう少し具体的に見ていきます。

対処① 依存パッケージの“世代”をそろえる

最も重要かつ王道の解決策が、ターゲットフレームワークに合わせて依存パッケージの世代をそろえることです。

.NET 6 アプリの場合

ターゲットフレームワークが net6.0 の場合、原則として NuGet パッケージも 6.x 世代にそろえます。 特に今回のケースでは、次のようなセットになります。

  • Microsoft.AspNetCore.Authentication.Negotiate 6.x
  • System.DirectoryServices.Protocols 6.x
  • System.DirectoryServices 6.x (必要な場合のみ)

典型的な .csproj の例は次のようになります。


<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate"
                      Version="6.0.14" />
    <!-- 明示的に指定したい場合のみ。基本は自動解決でも良い -->
    <PackageReference Include="System.DirectoryServices.Protocols"
                      Version="6.0.1" />
  </ItemGroup>
</Project>

もしここに System.DirectoryServices.Protocols 7.0.0 や System.DirectoryServices 7.0.0 が混ざっていたら、 まずはそれらを削除し、6.x にそろえることを検討してください。

.NET 7 アプリの場合

ターゲットフレームワークが net7.0 であれば、逆に 7.x 世代で統一するのが自然です。

  • Negotiate パッケージ → 7.x
  • DirectoryServices 系 → 7.x

既存のプロジェクトを .NET 7 に上げたものの、一部パッケージだけ 6.x のまま残っていると、その逆パターンでトラブルになることがあります。 バージョンをそろえる際は、Negotiate 側だけでなく DirectoryServices 側も含めて「世代」を合わせるイメージでチェックしましょう。

対処② NuGet に任せて自動解決させる

意外と効果的なのが、「自分で頑張って追加した DirectoryServices 系の参照をいったん全部消す」という方法です。

Negotiate パッケージは本来、必要な依存パッケージ (この場合 System.DirectoryServices.Protocols 6.0.1 など) を トランジティブ依存関係として自動で引き込んでくれます。 そのため、開発者側で System.DirectoryServices や System.DirectoryServices.Protocols を 手動追加する必要はないケースがほとんどです。

具体的な手順

  1. Visual Studio でプロジェクトの「依存関係 > NuGet」から
    • System.DirectoryServices
    • System.DirectoryServices.Protocols
    を手動で追加している場合は、一度すべて削除します。
  2. コマンドラインを使う場合は、次のように削除します。 dotnet remove package System.DirectoryServices dotnet remove package System.DirectoryServices.Protocols
  3. その上で、Negotiate を入れ直します。 dotnet add package Microsoft.AspNetCore.Authentication.Negotiate --version 6.0.14
  4. dotnet restore を実行し、依存関係を再取得します。

こうすることで、「Negotiate が要求する正しいバージョンだけが入ったクリーンな状態」から再スタートできます。 余計な手動参照が消えるだけでも、多くの競合が解消されます。

対処③ バージョン固定で衝突を回避する

現実のプロジェクトでは、 「どうしても外せない社内共通ライブラリが System.DirectoryServices.Protocols 7.x に依存している」 といった事情もあります。その場合は、アプリ側の .csproj で 明示的に使用するバージョンを固定してしまうのも一つの手です。

.csproj でのバージョン固定例


<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate"
                    Version="6.0.14" />

  <!-- ここで Protocols のバージョンを 6.0.1 に固定する -->
  <PackageReference Include="System.DirectoryServices.Protocols"
                    Version="6.0.1" />
</ItemGroup>

NuGet の解決ルールでは、アプリ本体のプロジェクトに書かれたバージョン指定が最優先となるため、 上流ライブラリが 7.x を要求していても、最終的にはここで指定した 6.0.1 が使われるようになります。

ただし、この方法は「本来 7.x を想定して作られているライブラリに、6.x を無理やり使わせる」ことにもなり得るため、 実行時テストを入念に行うことが前提です。あくまで「短期的な回避策」として使い、可能であれば 最終的にはライブラリ側も含めて同じ世代にそろえることをおすすめします。

依存関係の可視化:dotnet list package --include-transitive

どこからどのバージョンが入ってきているのかを確認するには、次のコマンドが非常に便利です。


dotnet list package --include-transitive

これにより、プロジェクトで参照しているパッケージだけでなく、 それらを経由して引き込まれている「間接参照 (トランジティブ) のパッケージ」も一覧表示されます。

ここで System.DirectoryServices.Protocols の行を探し、 自分の意図しない 7.x が混ざっていないか、あるいは 6.x と 7.x が共存していないか を確認しましょう。

対処④ 発行時のトリミング (Trim Unused Code) を無効化する

「開発環境では動くのに、発行した本番環境だと FileNotFoundException が出る」という場合、 コードトリミング が原因になっていることがあります。

ASP.NET Core の発行設定で「未使用コードのトリミング」が有効になっていると、IL リンカーが 「参照されていない」と判断したアセンブリやメソッドを削除します。 しかし、Negotiate 認証や DirectoryServices 系は、反射やランタイム動的ロードを使うケースもあり、 トリマーから見ると「使っていないように見える」ことがあります。 その結果、System.DirectoryServices.Protocols.dll 自体が出力から削られ、 本番でのみ FileNotFoundException が発生することになります。

Visual Studio での設定例

Visual Studio から発行している場合は、Publish プロファイルの設定画面で 次のような項目を確認します。(実際の表記は環境により多少異なります)

  • 発行プロファイルの「設定の編集」または「詳細設定」を開く
  • 「未使用コードのトリミング (Trim unused code)」のチェックを外す
  • 再度発行する

.csproj での設定例

CLI で発行している場合や、確実にトリミングを無効にしたい場合は、 .csproj に次のような設定を追加します。


<PropertyGroup>
  <PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>

逆に、どうしてもトリミングを有効のまま運用したい場合は、

  • System.DirectoryServices.Protocols を「必ず残す」ようにトリマー設定を追加する
  • 反射に依存する箇所にダイナミック依存のヒントを与える

といった高度な調整も可能ですが、複雑になりがちなので、まずは 一度トリミングを無効化して問題が解消するかを確認するのが現実的です。

対処⑤ その他のチェックポイント

上記の対処を行ってもまだ挙動が不安定な場合、次のような基本的なチェックも有効です。

dotnet clean と dotnet restore の実行

古いビルド成果物やキャッシュが悪さをしていると、パッケージ構成を変更しても 期待通りに反映されないことがあります。次のコマンドを一度実行してから再ビルドしてみてください。


dotnet clean
dotnet restore
dotnet build

出力フォルダに DLL が含まれているか確認

ビルド・発行後の出力フォルダ (例:bin\Release\net6.0\publish) を開き、 System.DirectoryServices.Protocols.dll が存在するかを直接確認します。

  • DLL 自体が存在しない → 依存関係の解決またはトリミング設定に問題がある
  • DLL はあるのに FileNotFoundException → バージョン競合、ロードパス、実行環境の違いなどを疑う

実行環境の違いを切り分ける

IIS 経由で動かしている場合と、dotnet run / Kestrel で直接起動する場合とで挙動が異なるケースもあります。

  • IIS 管理下のアプリケーションプールの .NET ランタイムバージョン
  • 環境変数 ASPNETCORE_ENVIRONMENT
  • Self-contained / Framework-dependent 発行の違い

などの差分がないかも合わせて確認しておきましょう。

自作ライブラリ・社内共通ライブラリが原因の場合

少しややこしいケースとして、次のような構成があります。

  • Web アプリ本体:net6.0
  • 社内共通ライブラリ A:System.DirectoryServices.Protocols 7.x に依存
  • Web アプリ側で Negotiate 6.x を追加

この場合、アプリ本体は 6.x を使いたいが、ライブラリ側が 7.x を要求しているという状態になり、 結果として「6.x を要求する Negotiate」と「7.x を要求する共通ライブラリ」が競合することになります。

このケースで考えられる選択肢は次の通りです。

  1. プロジェクト全体を .NET 7 以降に上げて、7.x に統一する
    将来を見据えるなら最も筋の良い解決策です。Web アプリも共通ライブラリも 7.x 世代にそろえてしまいましょう。
  2. 共通ライブラリに net6.0 用のターゲットを追加する
    ライブラリを複数ターゲット (例:net6.0;net7.0) とし、 net6.0 ターゲットでは DirectoryServices 6.x を参照するように分岐させる方法です。
  3. 短期的には、③ のようにバージョン固定でねじ伏せる
    ただし長期運用には向かないため、将来的には 1 または 2 を目指すべきです。

いずれにしても、「アプリ本体」と「ライブラリ」の両方を含めて、 どの世代の DirectoryServices.* を使うのかを設計レベルで決めることが重要です。

よくある誤解・アンチパターン

「最新版を入れておけば安全」という思い込み

NuGet パッケージを更新する際によくやってしまうのが、 「とりあえず最新版 (7.x, 8.x) を入れておけば安心」という発想です。 しかし、.NET のランタイムは 異なるメジャーバージョンを完全な別物として扱うため、 6.x を前提に設計されたコンポーネントに 7.x を混ぜると、かえって不安定になります。

特に、System.* から始まる BCL 系パッケージはフレームワークの一部として扱われることが多く、 ターゲットフレームワークと世代を合わせる、というルールを強く意識してください。

「bindingRedirect で何とかする」

.NET Framework 時代には app.config / web.config の <bindingRedirect> でアセンブリのバージョン差を吸収することが一般的でした。

しかし、.NET Core / .NET 5+ 以降では bindingRedirect は基本的に使用されません。 そのため、今回のような問題を bindingRedirect で解決しようとしても効果はなく、 最終的には「依存パッケージの構成を正す」方向で対処する必要があります。

GAC (グローバルアセンブリキャッシュ) に DLL を突っ込む

同様に、.NET Framework のノリで「GAC に System.DirectoryServices.Protocols.dll を登録すればよいのでは?」 と考えてしまう場合もありますが、.NET Core / .NET 5+ 以降では GAC は基本的に前提になっていません。

現代的な .NET アプリでは、「必要な DLL はすべてアプリの出力フォルダに含める」ことが基本です。 GAC に頼るのではなく、NuGet と発行設定を通じて依存 DLL をきちんと持ち歩くようにしましょう。

まとめ:エラーの本質と解決の指針

ここまで見てきたように、今回の System.IO.FileNotFoundException: Could not load file or assembly 'System.DirectoryServices.Protocols' の本質は、

「Negotiate が要求する System.DirectoryServices.Protocols 6.x と、
プロジェクト/ライブラリが持ち込む 7.x との世代競合」

にあります。これを踏まえたうえで、実際の対処手順をまとめると次のようになります。

  1. ターゲットフレームワークに合わせてパッケージの世代を統一する
    .NET 6 なら 6.x、.NET 7 なら 7.x と、フレームワークと同じメジャーバージョンで統一する。
  2. 手動で追加した DirectoryServices 系を整理し、NuGet に自動解決させる
    不要な System.DirectoryServices.* を一度全部外し、Negotiate を入れ直してクリーンな依存関係にする。
  3. やむを得ない場合は .csproj で Protocols のバージョンを固定する
    <PackageReference Include="System.DirectoryServices.Protocols" Version="6.0.1" /> のように明示して衝突を抑える。
  4. 公開環境だけで起きる場合はトリミング設定を疑う
    PublishTrimmed や「Trim Unused Code」の設定を見直し、必要に応じて無効化する。
  5. dotnet list package --include-transitive や bin フォルダの中身を確認する
    どこからどのバージョンが入ってきているのか、DLL 自体が出力されているのかを目視で確認する。

Negotiate 認証は、オンプレミスの Active Directory と連携した社内システムなどでは今もよく利用される機能です。 本記事のポイントを押さえておけば、「パッケージを追加したら急に System.DirectoryServices.Protocols が見つからなくなった」 といったトラブルにも落ち着いて対処できるようになります。もし同様のエラーに悩まされている場合は、 まずはプロジェクト全体の「世代」をそろえるところから始めてみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次