[.NET 6] 認証情報を redis に保持しログアウト時に無効化する

最近認証回りをちょこちょこ調べていて、しばやんさんの記事をベースに .NET 6 でおためし

[ASP.NET Core アプリケーションのログアウト時に認証クッキーを確実に無効化する](https://blog.shibayan.jp/entry/20210308/1615173991)

# やりたいこと
- 認証情報を redis に保持する
- ログアウト時に redis の値を削除し認証情報を無効にする

# 環境
- Visual Studio 2022 / .NET 6
- redis on docker

## モジュール構成
```
Sample.csproj
└ Controllers
  └ AccountController.cs
  └ ValuesController.cs
└ RedisSessionStore.cs
└ Program.cs
```

## コード
``` AccountController.cs

        public IActionResult Login()
        {
            var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, "test taro"),
                new Claim(ClaimTypes.Role, "Administrator")
            }, CookieAuthenticationDefaults.AuthenticationScheme));
            var authenticationProperties = new AuthenticationProperties
            {
                IsPersistent = true,
                RedirectUri = Url.Action("Index", "Home")
            };

            return SignIn(claimsPrincipal, authenticationProperties, CookieAuthenticationDefaults.AuthenticationScheme);
        }

        public IActionResult Logout()
        {
            var authenticationProperties = new AuthenticationProperties
            {
                RedirectUri = Url.Action("Index", "Home")
            };

            return SignOut(authenticationProperties, CookieAuthenticationDefaults.AuthenticationScheme);
        }

```
- 単純なログイン／ログアウト処理を定義
- 認証情報の取り扱いを確認したいので、固定の Claim を使用する


``` ValuesController.cs

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public ActionResult Get() => Ok(new { name = User?.Identity?.Name });
    }

```
- こちらも、単純に認証情報を取得するだけの処理を定義


``` RedisSessionStore.cs

    public class RedisSessionStore : ITicketStore
    {
        private readonly IDistributedCache redisCache;

        public RedisSessionStore(IDistributedCache redisCache) => this.redisCache = redisCache;

        // 認証情報を保存する
        public async Task<string> StoreAsync(AuthenticationTicket ticket)
        {
            // Guid をキーに認証情報を redis に保存する
            var key = Guid.NewGuid().ToString();
            await RenewAsync(key, ticket);
            return key;
        }

        // 認証情報を更新する
        public async Task RenewAsync(string key, AuthenticationTicket ticket)
        {
            // 有効期限を 5 分にして保存する
            var serializedTicket = TicketSerializer.Default.Serialize(ticket);
            await redisCache.SetAsync(key, serializedTicket, new DistributedCacheEntryOptions() { SlidingExpiration = TimeSpan.FromMinutes(5) });
        }

        // 認証情報を取得する
        public async Task<AuthenticationTicket?> RetrieveAsync(string key)
        {
            // キーに該当する認証情報を取得。なければ null を返す
            var serializedTicket = await redisCache.GetAsync(key);
            return serializedTicket != null ? TicketSerializer.Default.Deserialize(serializedTicket) : null;
        }

        // 認証情報を削除する
        public async Task RemoveAsync(string key) => await redisCache.RemoveAsync(key);
    }

```
- ITicketStore を実装し、各メソッドで redis の操作を行う


``` Program.cs

builder.Services.AddSingleton<IDistributedCache, RedisCache>();
builder.Services.AddSingleton<ITicketStore, RedisSessionStore>();
builder.Services.AddStackExchangeRedisCache(options =>
{
    // docker で起動した redis を指定する
    options.Configuration = "127.0.0.1:6379";
});

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        // こちらも有効期限を 5 分で設定
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
    });
builder.Services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
    .Configure<ITicketStore>((options, store) => options.SessionStore = store);

```
- nuget 参照で `Microsoft.Extensions.Caching.StackExchangeRedis` を追加しておく

## redis
``` docker-compose.yml

version: '3'

services:
  redis:
    container_name: redis
    image: redis:latest
    ports:
      - 6379:6379
    volumes:
      - ./data/redis:/data

```

# 実行
- /Account/Login にアクセス
    - Cookie の `.AspNetCore.Cookies` の値をコピー
- Postman から /api/values にリクエスト
    - Key = Cookie / Value = .AspNetCore.Cookies=xxxxxxxxxxxxxxx を設定
    - `"name": "test taro"` がレスポンス
- /Account/Logout にアクセス
- Postman から /api/values にリクエスト
    - Key = Cookie / Value = .AspNetCore.Cookies=xxxxxxxxxxxxxxx を設定
    - `"name": null` がレスポンス