JWTって何？

# はじめに
この記事では、JWTを使うにあたっての概念理解や実装方法をまとめたものになります。

# JWTとは？
JSON Web Token(JWT)とは、相互にデータをやり取りする主体同士で安全に情報を JSON オブジェクトとして転送するためのコンパクトで自己記述的な方法です。このJSON オブジェクトの情報はデジタル署名されているため、検証および信頼できるものになっています。

# 構成と作成方法
JWTは3つの部分で構成されています：

**1. ヘッダー（Header）**: 

トークンのタイプと使用されるハッシュアルゴリズムを指定
例：
```
{
  "alg": "HS256",
  "typ": "JWT"
}
```

このJSONはBase64Urlでエンコードされ、JWTの最初の部分を形成する。

例をエンコードした結果
```
eyJhbGciOiJIUZI1NiIsInR5cCI6IkpXVCJ9
```

**2. ペイロード（Payload）**: 

トークンに含まれるクレーム（要求）

:::note info
#### よく使用されるクレーム名

**1. iss (Issuer)**
- トークンの発行者を表します

**2.sub (Subject)**
- トークンの対象者（主語）を表します。通常、ユーザーの ID などが格納されます

**3.aud (Audience)**
- トークンの受信者を表します。特定のサービスやアプリケーションの識別子が含まれます

**4.exp (Expiration)**
- トークンの有効期限（UNIX タイムスタンプ形式）を表します

**5. nbf (Not Before)**
- トークンの有効開始時刻を表します。この時刻より前はトークンが無効になります

**6. iat (Issued At)**
- トークンが発行された日時を表します

**7. jti (JWT ID)**
- トークンの一意な識別子を表し、リプレイ攻撃を防ぐために使用されることがあります

:::


例：
```
{
  "sub": "1234567890",
  "name": "zono0013",
  "iat": 1733929200,
  "exp": 1734015600
}
```

このJSONもBase64Urlでエンコードされ、JWTの二つ目の部分を形成する。

例をエンコードした結果
```
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Inpvbm8wMDEzIiwiaWF0IjoxNzMzOTI5MjAwLCJleHAiOjE3MzQwMTU2MDB9
```

**3. 署名（Signature）**: 

トークンの整合性を保証

例：
1. `1` `2`で作成し、エンコードしたヘッダーとペイロードを`.`で区切り一つの文字列にする
1. ヘッダーで指定したハッシュアルゴリズムと秘密鍵を使用して、署名を生成する
:::note info
細かく説明すると、ハッシュ化する関数を秘密鍵を使って初期化を行い、
その後、ハッシュ化する。
:::

```
gCz6YTVuuKy-B0GWS-isIs_zGmxcSPeANPB2i31_rJs
```

**JWT構造**

最終的に今回の例のJWTは以下のものになる
```go
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // ヘッダー
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Inpvbm8wMDEzIiwiaWF0IjoxNzMzOTI5MjAwLCJleHAiOjE3MzQwMTU2MDB9. // ペイロード
gCz6YTVuuKy-B0GWS-isIs_zGmxcSPeANPB2i31_rJs // 署名
```
# 特徴
- JSONでやりとりできる点
- 著名があり改ざんを検出できる点

# 認証フロー


### Signup
```mermaid
sequenceDiagram
    participant Client
    participant Server
    participant DB
    Client->>Server: ユーザー情報で新規登録リクエスト
    Server->>DB: ユーザー情報を保存
    DB-->>Server: 保存完了
    Server->>Server: JWTトークンを生成
    Server-->>Client: トークンを返却
```

### Signin
```mermaid
sequenceDiagram
    participant Client
    participant Server
    participant DB
    Client->>Server: ログイン情報でリクエスト
    Server->>DB: ユーザー情報を検証
    DB-->>Server: 検証結果
    alt ログイン成功
        Server->>Server: JWTトークンを生成
        Server-->>Client: トークンを返却
    else ログイン失敗
        Server-->>Client: エラーレスポンス
    end
```

### 認証リクエスト
```mermaid
sequenceDiagram
    participant Client
    participant Server
    Client->>Server: JWTトークン付きでリクエスト
    Server->>Server: トークンを検証
    alt トークン有効
        Server-->>Client: リソースを返却
    else トークン無効/期限切れ
        Server-->>Client: 401 Unauthorized
    end
```

# Goでの実装

### トークンの生成
```go
func generateToken(userID int) (string, error) {
    claims := jwt.MapClaims{
        "sub" : userID,
        "name": userName,
        "ita" : time.Now(),
        "exp" : time.Now().Add(time.Hour * 24).Unix(), // 24時間有効
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // ヘッダーの追加（下記を参照してください）
    
    return token.SignedString([]byte("JWT_SECRET_KEY"))
}
```
:::note info
`jwt.NewWithClaims(jwt.SigningMethodHS256, claims)` を呼び出すと、`JWT` ライブラリが以下のヘッダーを自動的に生成してくれます。
```go
{
  "alg": "HS256",
  "typ": "JWT"
}
```
また、`jwt.NewWithClaims(jwt.SigningMethodHS256, claims)` の呼び出し後に以下のようなコードを追加することで、ヘッダーに情報を増やすことができる
```go
// ヘッダーをカスタマイズ
token.Header["kid"] = "custom-key-id"
```
:::


### トークンの検証
```go
func validateToken(tokenString string) (*jwt.Claims, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // 署名方法の検証
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method")
        }
        return []byte(os.Getenv("JWT_SECRET_KEY")), nil
    })

    if err != nil {
        return nil, err
    }

    if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
        // 追加の検証(有効期限内か？を確認)
        if exp, ok := claims["exp"].(float64); ok {
            if time.Now().Unix() > int64(exp) {
                return nil, errors.New("token has expired")
            }
        }
        return &claims, nil
    }

    return nil, errors.New("invalid token")
}
```

# 参考文献
https://jwt.io

https://zenn.dev/mikakane/articles/tutorial_for_jwt

https://qiita.com/knaot0/items/8427918564400968bd2b