ginでユーザーの認証処理を行う　~jwtトークン発行編~

# ginでユーザーの認証を行う

# はじめに
この記事ではユーザーの認証処理のうちのバックエンド側で行われる処理であるjwtトークンの発行方法について解説します。

# 目次
1. そもそもjwtトークンって何？
1. jwtトークン発行のプロセスの概要
1. 実際のコード

# そもそもjwtトークンって何？
## JWTとは何か？
- jwtトークンとは一言で表すと**セキュアな情報交換を実現するためのデジタルトークン**です。
このjwtトークンを使用することで**クライアント側で今どのユーザーがログインしているかの判断**を行うことができたり、**そのユーザーのAPIアクセスの認可**を行うことができたりします。
また、jwtトークンは３つの部分から構成されます。
    1. **ヘッダー**：使用するアルゴリズムとトークンのタイプ（通常はjwt）を含みます。
    1. **ペイロード**：トークンに含まれるクレーム（情報）を含みます。例えばユーザーIDや有効期限など。
    1. **署名**：ヘッダーとペイロードを秘密鍵で署名したものです。これによってjwtトークン全体が暗号化されてトークンの改ざんを防げます。

:::note info
実際のjwtトークンは以下のようになっています。
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IlVzZXIxQGdtYWlsLmNvbSIsImV4cCI6MTczODEzOTI1Niwic3ViIjo4OX0.nllm4EckwZ8mOKlvxoo8aNb67PhYzlFFVbHoGXt3dDk
```
このトークンが先ほど伝えた**ヘッダー**と**ペイロード**と**署名**の3つの部分に分かれています。
:::

# jwtトークン発行のプロセスの概要
- 今回はjwtトークン発行のプロセスについてのみ解説します。本来はクライアント側でjwtトークンを使用する方法もありますがそれはまた[次回の記事](https://qiita.com/kenshin_8799/items/69f2dbfd075a33d6d24b)でお話しします。

    jwtトークンの発行の全体の流れとしては下記のとおりです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3758326/975ecf6a-85ee-cef3-3167-f47d45c19ddd.png)

:::note warn
**SignUpの処理とかだと必要な工程は増える**のですが今回の記事の本質ではない部分なので今回はより本質に近いLogin処理のみを解説します
:::
# 実際のコード
## Login関数
- Login関数では主に下記の処理を行います。
    1. メールアドレスを基にしたユーザー検索
    2. パスワードが一致するか（ハッシュ化をしているのでそれに合わせて処理）
    3. ユーザーのIDとメールアドレスを基にjwtトークンを作成するCreateToken関数に渡す
```go:auth_service.go
func (s *AuthService) Login(email string, password string) (*string, time.Duration, error) {
	foundUser, err := s.repository.FindUserByEmail(email)
	if err != nil {
		return nil, 0, err
	}

	if foundUser.Password == nil {
		return nil, 0, errors.New("パスワードが設定されていません")
	}

	err = bcrypt.CompareHashAndPassword([]byte(*foundUser.Password), []byte(password))
	if err != nil {
		return nil, 0, err
	}

	jwtToken, tokenExpiry, err := s.CreateToken(foundUser.ID, foundUser.Email)
	if err != nil {
		return nil, 0, err
	}

	return jwtToken, tokenExpiry, nil
}
```
- **メールアドレスを基にしたユーザー検索**
    - 以下のコードでメールアドレスを基にユーザーを検索して返却します。
```go:auth_service.go
foundUser, err := s.repository.FindUserByEmail(email)
```
- **パスワードが一致するか（ハッシュ化をしているのでそれに合わせて処理）**
    - 以下のコードでパスワードが一致しているかを確認します。このプロジェクトではパスワードのハッシュ化を行っているのでそれに合わせて確認方法も少し変更しています。
```go:auth_service.go
err = bcrypt.CompareHashAndPassword([]byte(*foundUser.Password), []byte(password))
```
- **ユーザーのIDとメールアドレスを基にjwtトークンを作成するCreateToken関数に渡す**
    - CreateToken関数ではjwtトークンの作成と暗号化が行われています。以下のコードで引き渡しが行われています。
    - tokenExpiaryはのちほど使用します
```go:auth_service.go
jwtToken, tokenExpiry, err := s.CreateToken(foundUser.ID, foundUser.Email)
```

## CreateToken関数
- CreateToken関数では主に以下の処理を行います。
    1. jwtトークンの作成
    1. 秘密鍵を使用したjwtトークンの暗号化

```go:auth_service.go
func (s *AuthService) CreateToken(userId uint, email string) (*string, time.Duration, error) {
	var tokenExpiry time.Duration	
    
	tokenExpiry = time.Hour * 24 * 14 // 14日間

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"sub":   userId,
		"email": email,
		"exp":   time.Now().Add(tokenExpiry).Unix(),
	})

	tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY")))
	if err != nil {
		return nil, 0, err
	}

	return &tokenString, tokenExpiry, nil
}

```

- **jwtトークンの作成**
    - 以下のコードでjwtトークンのインスタンスを作成しています。ここではまだ暗号化されていません。
    - 今回は「ユーザーID」と「メールアドレス」と「有効期限」をjwtトークンに含めていますが**必ずしもこれらのみで構成されるわけではなく、ほかにもいくつかのパラメータが存在します**。

```go:auth_service.go
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"sub":   userId,
		"email": email,
		"exp":   time.Now().Add(tokenExpiry).Unix(),
	})
```

- **jwtトークンの暗号化**
    - 以下のコードで暗号化を行います。
    - ここでは秘密鍵を使用して暗号化を行います。**この秘密鍵は何でもよいですが一般的には文字列で複雑で長い文字列であることが推奨されます**。
```go:auth_service.go
tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY")))
```

## jwtトークンをクッキーにセットする
- 以下のコードでjwtトークンをクッキーにセットします。
```go:auth_repository.go
ctx.SetCookie("jwt-token", *jwtToken, int(tokenExpiry.Seconds()), "/", "localhost", false, true)
```
- ここでSetCookie関数の各引数に関して説明します。
1. **"jwt-token"**:
    - クッキーの名前です。ここでは[jwt-token]としています。
1. ***jwtToken**：
    - クッキーの値です。これは生成されたjwtTokenです。
1. **int(tokenExpiry.Seconds())**：
    - クッキーの有効期限です。秒単位で管理することができ、このためにtokenExpiaryをLogin関数から返却しています。

1. **"/"**：
    - クッキーが適用されるパスです。ここではルートパスが使用されているので指定されたドメイン（次の引数で説明しますがここでは"localhost"のこと）内のすべてのパスに対してクッキーが送信されることを表しています。

1. **"localhost"**：
    - クッキーが送信されるドメインです。ここでは「localhost」が指定されています。

1. **false**：
    - セキュアフラグ。このフラグが true の場合、クッキーはHTTPS接続でのみ送信されます。ここでは false に設定されています。

1. **true**：
    - HttpOnlyフラグ。このフラグが true の場合、クッキーはJavaScriptからアクセスできません。セキュリティを向上させるために使われます。

# まとめ
今回はjwtトークンの発行方法についての記事を書きました。これと対をなすjwtトークンを用いたAPIの認証やユーザーの取得についても[別の記事](https://qiita.com/kenshin_8799/items/69f2dbfd075a33d6d24b)で取り上げます。