C#でOpenAPIを使ってBearer認証をする

# 1. はじめに
* OpenAPIをトークンを使用して認証できるようにしたい
* Bearerトークンを発行するようにしたい

# 2. 開発環境
* C#
* .NET 6
* OpenAPI 3.0
* Visual Studio 2022
* Windows 11

# 3. 事前準備
* APIのプロジェクトに下記をNuGetでインストールする
    * Microsoft.AspNetCore.Authentication.JwtBearer
    * Microsoft.IdentityModel.JsonWebTokens
    * System.IdentityModel.Tokens.Jwt

# 4. OpenAPI Specification(OAS)の定義
* ```/token```でBearerトークンを発行するAPIを定義する
* ```security:```で全体に認証対象にしているが、```security: []```でトークンを発行するAのみ除外する

```yml
openapi: 3.0.0
info:
  title: API API Document
  version: 0.0.1
  description: API のドキュメントです
paths:
  /sample:
    get:
      operationId: GetSample
      responses:
        '200':
          description: OK
  /token:
    get:
      operationId: GetToken
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                    description: Bearerトークン
                required:
                  - token
      security: []
components:
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Credentials or access token for API
security:
  - Bearer: []
```

# 5. ソースコード
## 5.1. トークン発行コントローラー
```C#
[AllowAnonymous]
public override IActionResult GetToken()
{
    //token生成
    var tokenString = JwtBearerUtil.BuildToken();
    var response = new
    {
        token = tokenString
    };

    return Ok(response);
}
```

## 5.2. トークン発行ユーティリティ
* サンプルなので秘密鍵等を定数に定義したが、セキュリティの観点から外部ファイルにする必要がある
```C#
/// <summary>
/// JWT(Bearer)に関するユーティリティ
/// </summary>
public static  class JwtBearerUtil
{
    // 定数
    private const string ISSUER_SIGNING_KEY = "4a502f59-d434-4a92-8b5e-8bd23ee762dd";
    private const string VALID_ISSUER = "http://localhost:7188/";
    private const string VALID_AUDIENCE = "https://localhost:7188/";
    private const int EXPIRES_DAYS = 1;    // 有効期限：1日

    /// <summary>
    /// トークン作成
    /// </summary>
    /// <returns></returns>
    public static  string BuildToken()
    {
        // PAYLOADにログインIDを追加
        var claims = new List<Claim>();
        claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
        claims.Add(new Claim("loginId", "XXXXX"));

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ISSUER_SIGNING_KEY));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: VALID_ISSUER,
            audience: VALID_AUDIENCE,
            claims: claims,
            expires: DateTime.Now.AddDays(EXPIRES_DAYS),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public static string getIssuerSigningKey()
    {
        return ISSUER_SIGNING_KEY; 
    }

    public static string getValidIssuer() {
        return VALID_ISSUER;
    }

    public static string getValidAudience() {
        return VALID_AUDIENCE;
    }

    public static int getExpiresDaysy() {
        return EXPIRES_DAYS;
    }
}
```

## 5.3. プログラム
```C#:Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// JWTを登録する
builder.Services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(JwtBearerUtil.getIssuerSigningKey())),
        ValidateIssuer = true,
        ValidIssuer = JwtBearerUtil.getValidIssuer(),
        ValidateAudience = true,
        ValidAudience = JwtBearerUtil.getValidAudience(),
        RequireExpirationTime = true,
        ValidateLifetime = true,
        ClockSkew = TimeSpan.FromDays(JwtBearerUtil.getExpiresDaysy()),
    };
});

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options => {
    options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = Microsoft.OpenApi.Models.ParameterLocation.Header,
        Description = "JWT Authorization header using the Bearer scheme."
    });
    options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement {
        {
            new Microsoft.OpenApi.Models.OpenApiSecurityScheme {
                    Reference = new Microsoft.OpenApi.Models.OpenApiReference {
                        Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
                            Id = "Bearer"
                    }
                },
                new string[] {}
        }
    });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthentication();    // 追加
app.UseAuthorization();

app.MapControllers();

app.Run();
```

# 6. 補足
* 秘密鍵は何でもよいのだが自由があるだけに設定に悩み、下記サイトを参考にさせてもらった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/8c2d387f-694e-0817-f09e-16497beda212.png)

https://8gwifi.org/jwsgen.jsp

# 7. 参考文献
https://dev.to/moe23/net-6-minimal-api-authentication-jwt-with-swagger-and-open-api-2chh

https://www.c-sharpcorner.com/article/jwt-token-authentication-and-authorizations-in-net-core-6-0-web-api/?ref=morioh.com&utm_source=morioh.com

https://remix-yh.net/1689/

https://knooto.info/asp-net-core-3-applications-with-jwts/

https://qiita.com/epsilonGtmyon/items/9bcf79ef279e2003edf5

https://zukucode.com/2021/04/aspnet-jwt-auth.html

https://www.youtube.com/watch?v=IaCQqCIqZ6U

https://qiita.com/zaburo/items/7595d886116848521e6c

https://www.blakepell.com/blog/read-jwt-token-claims-in-aspnet-core

https://jwt.io/