YARPで.NET Framework 4.8とASP.NET Coreを併存するとセッション共有できない原因と対処法

YARP Upgrade Assistant/System.Web Adaptersで.NET Framework 4.8(旧ASP.NET MVC)とASP.NET Core(新.NET)を並走させたところ、Core側でセッションが常にnullになる――この現象は「設定漏れ」というより“前提の違い”が原因で起きることが多いです。なぜ起きるのか、そして現実的な解決策を整理します。

目次

起きている現象:Framework側では入っているのに、Core側では常にnull

段階的移行(Strangler Figパターン)として、フロントにASP.NET Coreを立て、YARP(Yet Another Reverse Proxy)で一部パスだけ.NET Framework 4.8の旧ASP.NET MVCへ転送する構成はよく採用されます。Upgrade AssistantやSystem.Web Adaptersのサンプル(UpgradeSample / eShopLegacyMVCなど)を見ると「セッション共有できている」ように見えるため、次のような期待を持ちがちです。

  • Framework側のSession_Startやログイン処理でSession["test"] = "Shared Session OK";を設定
  • Core側でも同じキーで取り出せるはず(例:HttpContext.Session.GetString("test"))

しかし実際には、Core側で読み出そうとすると常にnull、あるいは「セッションが構成されていない」例外になったりします。ここで重要なのは、“セッション”という言葉が同じでも、ASP.NET(.NET Framework)とASP.NET Coreのセッションは別の仕組みだという点です。

原因:.NET FrameworkのSessionとASP.NET CoreのSessionは、そのまま互換にならない

まず結論から言うと、同じブラウザ・同じドメインでアクセスしていても、FrameworkのSessionをCoreがそのまま読むことはできません。理由はシンプルで、両者のセッションは次の点で設計が異なるからです。

観点ASP.NET(.NET Framework 4.8)ASP.NET Core(.NET)
セッションの実体ASP.NETセッション状態(InProc / StateServer / SQLServer 等)セッションミドルウェア+分散キャッシュ(任意)
識別子のCookie例:ASP.NET_SessionId例:.AspNetCore.Session(既定)
データ形式System.Web側の保存形式(内部実装)ISessionの保存形式(実装依存)
互換性同一Cookieを見ていても中身は互換ではない(同一の「セッション」にはならない)

つまり、Framework側のASP.NET_SessionIdがブラウザに載ってCoreにも届いていたとしても、Coreはそれを使ってFrameworkのセッションストアへ勝手に問い合わせてはくれません。逆にCoreの.AspNetCore.SessionをFrameworkが理解することもありません。

サンプルで「動いている」ように見えるケースの多くは、次のいずれかです。

  • そもそも“セッション共有”ではなく、同一アプリ内(片側のみ)でセッションが見えている
  • System.Web AdaptersのRemote Session(CoreがFrameworkに取りに行く)が有効になっている
  • “共有しているのはセッションではなく認証(ログイン)”で、ユーザー情報を別経路で参照している

最初に整理したい:「共有したい」の正体は何か

「セッションを共有したい」という要求は、掘り下げると次のどれかに分かれます。ここを整理すると、最短ルートで解決できます。

  • ログイン状態(認証)を両方で成立させたい
  • ユーザーごとの小さな値(言語設定、ABテストの割当、ウィザードの進捗など)を渡したい
  • カート、入力途中フォーム、権限一覧などのある程度大きい状態を両方で参照したい
  • 旧アプリのコードが大量にあり、当面はFrameworkのセッションに依存したままCoreで一部画面だけ作りたい

このうち「小さな値を渡したい」だけなら、セッションに固執せずCookieで渡すのが最も堅実です。大きい状態なら共通ストア(DB/Redis)へ寄せるのが一般解です。どうしてもFrameworkセッションに依存したままCore側で読みたい場合に限り、System.Web AdaptersのRemote Sessionを検討します。

どの解決策を選ぶべきか(比較表)

移行期の現場では「理想」より「事故りにくさ」が重要です。目的別に選ぶための目安を表にまとめます。

解決策向いているケースメリットデメリット/注意
Cookieで必要な値だけ渡す小さな状態、移行期のつなぎ、相関ID最小実装で動く。障害に強い。YARPと相性が良い機密情報は入れない。改ざん対策が必要
共通ストア(DB/Redis)へ寄せるカート等の業務状態、複数台運用、長期運用両アプリで同じデータを参照でき、最終形にも近いデータ設計と運用(TTL/削除/監査)が必要
Remote Session(System.Web Adapters)既存のSession依存が強く、短期で画面だけCore化したい旧コードを変えずに段階移行できる場合がある構成が複雑。到達性やキー不一致でnullになりやすい

解決策A:小さな共有データならCookieに置き換える(最もシンプル)

“セッション共有”にこだわらず、必要な値だけをCookieに入れて渡す方法です。YARPで並走する構成と相性がよく、実装も障害対応も分かりやすいのが強みです。

実装イメージ(Framework側:Cookieへ書き込む)

例として、Framework側で値を作り、HttpOnly Cookieとして返すコードを示します(ログイン成功時など、適切なタイミングで実行してください)。

// .NET Framework (System.Web) 側の例
// using System;
// using System.Web;

var value = "Shared Session OK";

// 可能なら「値そのもの」ではなく、ユーザーID等の参照キーに寄せる
var cookie = new HttpCookie("LegacyShared", value)
{
    HttpOnly = true,
    Secure = true,           // HTTPS前提。開発環境でHTTPなら条件分岐
    Path = "/",
    SameSite = SameSiteMode.Lax
};

// 必要なら有効期限を明示(セッションCookieにしたいならExpiresは付けない)
cookie.Expires = DateTime.UtcNow.AddHours(1);

Response.Cookies.Add(cookie);

ポイントは次のとおりです。

  • Cookie名は用途が分かるものにし、既存のASP.NET_SessionIdと混同しない
  • HttpOnlyを付け、JavaScriptから読み取れないようにする
  • 本番は原則Secure(HTTPS)
  • SameSiteは要件に合わせて調整(後述)

実装イメージ(Core側:Cookieを読む)

ASP.NET Core側では、コントローラやミドルウェアでCookieを読み取れます。

// ASP.NET Core 側の例
// using Microsoft.AspNetCore.Mvc;

var value = Request.Cookies["LegacyShared"];

if (!string.IsNullOrEmpty(value))
{
    // value を使って処理
}

改ざん対策:署名付きCookieにする(おすすめ)

Cookieはユーザーが内容を見たり書き換えたりできます。移行期でも、最低限の改ざん対策として署名(HMAC)を付けておくと安心です。実装の考え方は「ペイロード(値)+期限+署名」をセットにするだけです。

// 署名付きトークンの概念例(疑似コード)
// token = Base64(payload) + "." + Base64(exp) + "." + Base64(HMAC(payload + "." + exp, secret))

// payload例:userId や correlationId など(機密情報は入れない)

署名付きにしておくと、Core側で「値が改ざんされていない」ことを確認してから処理できます。値そのものを秘匿したい場合は暗号化も検討しますが、移行期は「参照キー+署名」で足りることがほとんどです。

Cookieに入れる値の設計が重要

Cookieは便利ですが、クライアント(ブラウザ)に保存される点がセッションと決定的に違います。次の方針にすると安全に運用しやすくなります。

  • 機密情報(権限一覧・個人情報・アクセストークン等)をそのまま入れない
  • 可能なら「値」ではなく「参照キー」を入れ、サーバー側のDB/キャッシュから取得する
  • 改ざんされる前提で、署名(HMAC)や暗号化を行う
やりたいことおすすめのCookie設計理由
ユーザーを特定したいユーザーIDやサロゲートキーのみ(署名付き)値の漏えい時の影響を最小化できる
一時的なフラグを渡したい短い文字列+短い有効期限セッションより障害に強く、移行期間に向く
画面遷移の相関を取りたいランダムな相関ID(サーバー側に状態を保存)Cookieは「鍵」、状態はサーバー側に置ける

SameSite / Secure / Domainの落とし穴

「Cookieで渡したのにCore側で見えない」場合、値より先にCookie属性を疑ってください。特にクロスサイト遷移が絡むとSameSiteに引っかかります。

項目典型的な症状対処
SameSite一部の遷移だけCookieが送られない要件に応じてLax/Noneを選択(NoneならSecure必須)
SecureHTTP環境だとCookieが送られない開発はHTTP/HTTPSを揃える、または環境で切替
Domain/Path特定パス配下でしか見えない同一ドメイン配下で揃える。Pathを「/」にする

解決策B:サーバー側に状態を持ちたいなら、共通ストアへ寄せる(SQL Server / Redis)

「2つのアプリから同じ状態にアクセスしたい」が本質なら、セッションという枠を外して、共通の保存先(DB/分散キャッシュ)を用意するのが王道です。特に将来的に水平スケールするなら、InProcセッションより遥かに運用しやすくなります。

方針:セッションIDを共有するより、参照キーを共有する

ありがちな失敗は「FrameworkのセッションIDをCoreでも使えるようにしたい」と考えることです。互換の壁を越えるコストが高く、移行期間が長引くほど負債になります。おすすめは次の設計です。

  • ログイン済みならユーザーIDをキーにする
  • 未ログインのウィザード等は、相関ID(GUID等)をCookieで持たせる
  • 状態本体はDB/Redisに保存し、両アプリから同じキーで参照する

.NET Framework側:セッションストアをSQL Serverに寄せる(参考)

旧ASP.NETはweb.configでSQL Serverセッションを使えます。ただしこれは「Framework内での共有・スケール」に効く設定であり、Coreと同一セッションにするための設定ではありません(ここが重要です)。

<!-- web.config の例(概念) -->
<system.web>
  <sessionState mode="SQLServer"
                sqlConnectionString="data source=...;initial catalog=ASPState;integrated security=true"
                cookieless="UseCookies"
                timeout="20" />
</system.web>

ASP.NET Core側:分散キャッシュ+状態参照(例)

Core側は分散キャッシュ(SQL Server / Redis)を使って状態を保存できます。セッションミドルウェアを使うこともできますが、移行期は「自前のキーでDB/キャッシュ参照」に寄せたほうが見通しが良いケースが多いです。

// ASP.NET Core 側:Redisを分散キャッシュとして使う例(概念)
// using Microsoft.Extensions.Caching.StackExchangeRedis;

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "your-redis:6379";
    options.InstanceName = "shared:";
});

// 例:ユーザーIDをキーにして状態を保存/取得する(疑似コード)
// await cache.SetStringAsync($"cart:{userId}", json, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) });
// var json = await cache.GetStringAsync($"cart:{userId}");

ここでも繰り返しになりますが、FrameworkのSessionとCoreのSessionを“同じもの”にするのは目的ではありません。共通ストアに寄せることで、両者が同じデータを参照できるようにするのが狙いです。

共有ストア向いている用途注意点
SQL Server永続性が必要、監査や参照が必要高頻度更新は負荷が上がりやすい。設計で緩和
Redis高速な一時データ、セッション代替、レート制限永続化/冗長化の設計が必要。TTL前提で使う
アプリDB(自前テーブル)業務状態(カート、申請の下書き等)「セッション」ではなく業務データとして扱う必要

解決策C:System.Web AdaptersのRemote Sessionを使う場合の前提と落とし穴

System.Web Adaptersには、ASP.NET Core側から旧ASP.NET側の機能を段階的に利用するための仕組みがあります。その中に「Remote Session」と呼ばれる考え方があり、CoreがFrameworkにHTTPで問い合わせて、Framework側のセッション値を取得する構成を取れます。

ただしこれは「同一セッションを互換共有する」のではなく、あくまでCoreがFrameworkのセッションに“取りに行く(プロキシ)”方式です。設定が少しでもずれると、Core側でnullになりやすいので、次の前提を満たしているかを順に確認してください。

前提:Core側は“古い書き方”でセッションを読まない

ASP.NET Coreでは標準でSystem.Webが存在しません。System.Web Adaptersで互換APIが提供されることはありますが、移行後も保守する前提なら、Core側は基本的に次の書き方へ寄せるのが安全です。

  • Core側:HttpContext.Session.GetString("key") / SetString
  • Framework側:Session["key"]

また、Core側でセッションを使うには、AddSession()とUseSession()、そしてミドルウェア順序が必要です。ミドルウェア順序が崩れると、取得できない/保存されないといった症状になります。

前提:RemoteAppUrl(到達先)とApiKeyが正しい

Remote Sessionは、CoreアプリがFrameworkアプリに到達できることが必須です。ここでハマりやすいのが、YARP構成特有の「どのURLを向けるべきか」です。

  • 推奨:Core→Frameworkへの問い合わせは、Frameworkの実体(内部ポート/内部URL)へ直接向ける
  • 非推奨:Coreの表向きURL(YARP経由のURL)を指定してしまい、プロキシが自分自身に戻ってループする
  • RemoteAppUrlが空、https/httpが不一致、証明書が信頼されていない、といった理由で問い合わせが失敗する

概念的な設定例(Core側)

実際の拡張メソッド名やパッケージ構成は導入時期で差が出るため、ここでは“何を揃えるべきか”が伝わる形で例を示します。

// ASP.NET Core 側 Program.cs の概念例
// using Microsoft.AspNetCore.SystemWebAdapters;

builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();

builder.Services.AddSystemWebAdapters()
    .AddRemoteAppClient(options =>
    {
        options.RemoteAppUrl = new Uri("https://legacy-app-internal/");
        options.ApiKey = "same-api-key";
    });

// 必要なアダプタ機能(Sessionなど)を追加する構成になる

var app = builder.Build();

app.UseForwardedHeaders();  // 逆プロキシ配下なら早い段階で
app.UseRouting();

app.UseSession();           // ルーティング後~エンドポイント前の位置に置くのが基本
app.UseSystemWebAdapters(); // Adaptersのミドルウェア(導入している場合)

app.MapControllers();
app.Run();

Framework側の確認ポイント

  • Remote Session用のエンドポイントが有効になっているか(未構成ならCoreは取得できません)
  • ApiKey等の共有秘密が一致しているか
  • Framework側セッションが有効で、対象ユーザーのセッションが実際に作られているか

YARP併存構成で特に起きやすい失敗パターン

失敗パターンCore側の症状確認ポイント
RemoteAppUrlがYARPの表URLを指しているnull、またはタイムアウトCore→Frameworkの内部到達先になっているか(ループしていないか)
ApiKey不一致null(内部的に401/403)Core/Framework双方のキー、環境変数の上書き、デプロイ差分
Framework側でセッションが生成されていない取得できても値が無い該当ユーザーのリクエストがFramework側を一度通っているか
負荷分散でスティッキー無し+InProc時々取れる/取れないFramework側セッションの保存先(InProcなら同一ノード固定が必要)
Destination Addressや到達先が誤っているnullYARPのDestinationが空/誤りで、結果としてRemote到達が失敗していないか

Remote Sessionは「既存コードへの影響を抑えて一部画面をCore化する」には有効ですが、構成が複雑になりやすいのも事実です。移行期間が長いほど、Cookieや共通ストア方式のほうが最終的に楽になることが多いです。

切り分けの実践:どこでnullになっているかを短時間で特定する

現場で役立つ切り分け手順を、チェックリストとしてまとめます。ログやブラウザ開発者ツール(Network / Cookies)と合わせて確認すると、原因を早く特定できます。

チェック見る場所分かること
Core側でセッションミドルウェアが有効かProgram.cs / Startup.csAddSessionとUseSessionの有無、順序ミス
どのAPIでセッションを読んでいるかCore側コードSystem.Web系の前提で読んでいないか、移行しやすい書き方か
ブラウザが送っているCookieは何か開発者ツール(Request Headers)ASP.NET_SessionIdが来てもCoreは読まない、などの誤解を潰せる
Cookie属性が要件に合っているかSet-CookieレスポンスSameSite/Secure/Domain/Pathの不一致を発見できる
Remote Sessionの問い合わせが成功しているかCore側ログ(HTTPクライアント)RemoteAppUrlの誤り、401/403、証明書エラーを発見できる
Framework側セッションがどこに保存されているかweb.config / インフラInProc+負荷分散での「時々null」を説明できる

移行を成功させる考え方:セッション共有より「認証共有+状態の外部化」

YARPで旧ASP.NETとASP.NET Coreを併存させる目的は、最終的にCoreへ寄せていくことです。その観点では、Framework固有のセッションに依存する設計を延命するほど、移行後に負債が残ります。

  • ログイン状態は可能な限り統一(同一IdP、同一認証基盤、またはトークンベース)
  • 状態はDB/キャッシュへ外部化し、ユーザーID等で参照する
  • どうしても必要な最小値だけ、署名付きCookieで受け渡す

結局のところ、FrameworkとCoreで「セッションをそのまま共有」しようとするほどハマりやすいのがこの領域です。まずはCookie方式(必要な値だけ)で問題を小さく解き、次に共通ストアへ寄せ、Remote Sessionは“移行期間のブリッジ”として限定的に使う――この順序が、実運用でトラブルになりにくい進め方です。

この記事を書いた人

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

コメント

コメントする

目次