はじめに
.NETをCloudRun上にデプロイした環境で、頻繁にIdentityのセッションが切れてしまう現象が発生します。
この問題の解決方法について、多少の需要があると思いましたので、記事にしました。
セッションが切断される理由
.NET Identityではトークン認証を使用しており、サーバー側で暗号化したトークンをCookieでやりとりすることでセッションの状態を管理しています。
この認証に使用される暗号化キーの保存先は、.NETのデータ保護システムに設定された場所に保存されます。デフォルトで暗号化キーはメモリに保存されます。
一般的な単一のオンプレサーバーやVMなどでは、再起動などをしない限りメモリがリセットされないため、問題は発生しません。
ただし、CloudRunのようなアクセス数によってコンテナが増減する場合は問題が発生します。
コンテナごとにメモリが別れているため、スケールインしてコンテナ数が0になった際にメモリがリセットされ、暗号化キーが失われてしまいます。
また、スケールアウトしてコンテナ数が増えた場合でも、新規作成されたコンテナのメモリに暗号化キーは保存されていないため、セッションが切れてしまいます。
つまり、セッションが切断される理由は、コンテナ型Webサーバーでコンテナの増減によって、メモリ内に保存されている暗号化キーが失われてしまうからです。
暗号化キーを別ストレージ(DBなど)に保存する
上記の問題は、暗号化キーの保存先であるデータ保護システムのデフォルト保存先をメモリから別ストレージに変更することで解決できます。
今回は例として、暗号化キーの保存先をDB(Postgresql)に変更します。MySQLでも同じ方法で保存先を変更できます。
依存パッケージのインストール
まず、対象のプロジェクトに移動し、以下のパッケージをNugetからインストールします。バージョンは各プロジェクトに合わせます。
dotnet add package Microsoft.AspNetCore.DataProtection.EntityFrameworkCore --version 9.0.0
https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/
コードの変更
次に、Program.cs
に以下の内容を追加します。
using Microsoft.AspNetCore.DataProtection;
// 略
builder.Services.AddDataProtection()
.PersistKeysToDbContext<ApplicationDbContext>();
var app = builder.Build();
// 略
次に、ApplicationDbContext
にIDataProtectionKeyContext
を継承し、DataProtectionKeys
プロパティを追加します。
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
public class ApplicationDbContext : IdentityDbContext, IDataProtectionKeyContext
{
// 略
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
}
マイグレーション反映
上記の追加が完了したら、マイグレーションの作成、DB反映をします。
下記コードはサンプルになります。マイグレーションコードは既存コードに合わせて実行してください。
# マイグレーションの発行
dotnet ef migrations add AddDataProtectionKeysTable --context ApplicationDbContext --output-dir Data/Migrations
# マイグレーションをデータベースに反映
dotnet ef database update --context ApplicationDbContext
確認
これでDBに DataProtectionKeys
というテーブルが作成されます。
ログイン時に作成された暗号化キーが保存されます。
終わりに
結構前からこの問題について調査していましたが、検索の仕方が悪かったのか、解決まで実は1年くらいかかっています。
それまでは、外部の認証プロバイダを使用するなりして誤魔化していましたが、記事の解決策のおかげで外部認証に依存せずに済みそうでハッピーです。