ASP.NET CoreのJWT認証メモ

# .NET CoreのJWT認証メモ
Jwtを使った認証周りのメモです。主にソースです。
認証そのもののメモは[こちら](https://qiita.com/epsilon_87_myo/items/9b54de9890950593ed1f)

調べたバージョンはASP.NET Core 3.1

使い方については他で検索したら出てくるのでここでは詳しく書きません。

## パッケージの準備

`Microsoft.AspNetCore.Authentication.JwtBearer` を追加します。
バージョンは`3.1.4`を使用しました。

## コードのメモ

こんな感じで使います。

```cs:startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //中略

    services.
        AddAuthentication(option =>
        {
            option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(option =>
        {

            //JwtSecurityTokenHandler.ValidateTokenPayloadを通すためにいろいろいるみたい。
            //検証はMicrosoft.IdentityModel.Tokens.Validatorsっていう静的クラスがある

            //audienceの検証
            option.TokenValidationParameters.ValidAudiences = new string[] { "sandbox" };

            //issuerの検証
            option.TokenValidationParameters.ValidIssuers = new string[] { "epsilon" };

            //デフォルトは5分になってて、期限過ぎてもしばらくは検証を合格してしまう。
            option.TokenValidationParameters.ClockSkew = TimeSpan.Zero;

            //とりあえずテキトーな鍵
            byte[] key = Encoding.UTF8.GetBytes("1234567890123456");
            SymmetricSecurityKey securityKey = new SymmetricSecurityKey(key);
            option.TokenValidationParameters.IssuerSigningKey = securityKey;
        });
}
```

発行側

```cs
//payloadに含めるクレーム一覧
List<Claim> claims = new List<Claim>
{
    new Claim(ClaimTypes.Name, "Taro")
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims);

//署名用のキー
//TokenValidationParameters.RequireSignedTokensをfalseにすれば、署名なしでも使用可能になるがセキュリティ的にダメ
byte[] key = Encoding.UTF8.GetBytes("1234567890123456");
SymmetricSecurityKey securityKey = new SymmetricSecurityKey(key);
SigningCredentials cred = new SigningCredentials(securityKey, "HS256");

JwtSecurityTokenHandler th = new JwtSecurityTokenHandler();
JwtSecurityToken jst = th.CreateJwtSecurityToken(
    issuer: "epsilon",
    audience: "sandbox",
    subject: claimsIdentity,
    notBefore: null,//nullのときは現在時刻が使われるようだ
    expires: DateTime.Now.AddMinutes(5),//とりあえず短め
    issuedAt: null,//nullのときは現在時刻が使われるようだ
    signingCredentials: cred
    );
//このtokenをクライアントに返す
string token = th.WriteToken(jst);
```

## 登場するクラスメモ

依存している`System.IdentityModel.Tokens.Jwt` は`AzureAD` 側で管理されているようだ

リポジトリは[こちら](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet)

### JwtBearerHandler
ソースは[こちら](https://github.com/dotnet/aspnetcore/blob/v3.1.4/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs)

認証周りのポイントである`IAuthenticationHandler` のJwtの場合の実装クラスです。

- SingIn
- SingOut

は実装されていないのでトークンの発行は`HttpContext` 経由ではなく自身で行う必要があります。

#### `HandleAuthenticateAsync` メソッド
認証情報を取り出している部分です。

イベントの`MessageReceived` を経由することで`Authorization` ヘッダー以外からでもトークンを取り出すことができるようです。(ajaxではなくformをsubmitするような場合に使うのかな？)

イベントのリスナー登録は `JwtBearerOptions` から可能なはず。(試していません。)

`Authorization` ヘッダーが存在した場合、`Bearer ` 以降の文字列をトークンとして扱うようです。
もしトークンが取得できなかった場合は認証情報を復元しないようです。

その後はトークンの検証が行われます。検証についてのバリデーションの設定は[JwtBearerOptions.SecurityTokenValidators ](https://github.com/dotnet/aspnetcore/blob/v3.1.4/src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs#L93)で行います。
デフォルトでは`JwtSecurityTokenHandler`のみがセットされています。

### JwtSecurityTokenHandler
ソースは[ここ](
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/5.5.0/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs)

ポイントとなるクラスかなと個人的には思っています。

- `JwtBearerHandler` からは 検証、復元のために `ValidateToken` メソッドが呼ばれています。
- トークンの生成のためには `CreateJwtSecurityToken`, `WriteToken` などを使用します。

#### `ValidateToken` メソッド
トークンの検証および`ClaimsPrincipal` の返却を行います。

トークンをピリオドで分割した後のパート数によって`JWE` かどうかの判別をしているようです。

署名部分の検証を`ValidateSignature`で行い、ペイロード部分の復元、検証を`ValidateTokenPayload`で行っています。

[ValidateTokenPayload](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/5.5.0/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs#L734) では文字通りペイロード部分の検証を行います

多くのものは[Validators](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/5.5.0/src/Microsoft.IdentityModel.Tokens/Validators.cs)クラスのstaticメソッドに処理を委譲しています。

また検証の際のオプションは`JwtBearerOptions.TokenValidationParameters`で設定することができます。

#### `CreateJwtSecurityToken` メソッド
`JwtSecurityToken` を作るためのメソッドです。
いくつかオーバーロードされているので使いたいものを選べばよいと思います。

`SigningCredentials` を省略した場合、署名なしのトークンが作られますが、さすがにそれはセキュリティ的にアウトだと思います。

まだ私もどれが適切なのか判断できていません。

これ以外にもトークンを作るメソッドはあります。

#### `WriteToken` メソッド
`JwtSecurityToken` を`string` にするためのメソッド

## TokenValidationParameters
ソースは[こちら](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/5.5.0/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs)

トークンの検証/復元において

- 検証そのものの有効化/無効化
- 「有効な値」と判断するものの設定

などをすることができます。

一部の物しか試せていませんが一つだけ紹介します。

### `ClockSkew` プロパティ
`TimeSpan`型のプロパティでデフォルトは5分に設定されています。

[Validators.ValidateLifetime](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/5.5.0/src/Microsoft.IdentityModel.Tokens/Validators.cs#L248)メソッドで使用されており

時間計算の際に時刻をずらすために使われているようです。

なので例えば`expires` の時間を過ぎてもすぐにはトークンは無効とはならず。この`ClockSkew`の期間だけ猶予があるようです。


## 終わりに
他にもたくさんのクラスが用意されているので、まだまだいろいろすることができそうです。

MSのドキュメントが見当たらないのでもしよければコメントで教えてください。