【Go言語】Ginでログイン認証を実装する

## はじめに
今回は、Go言語のWebフレームワーク「Gin」を用いてログイン認証を実装していこうと思います。
本記事では、エンドポイントの構築、JWT（JSON Web Token）による認証、MySQLデータベースとの連携方法について詳しく説明します。
まずは基本的なセットアップから始め、順を追って実装していきましょう！

## 環境
・MacBook Air M1

## 使用技術
- VScode
- Go 
    - バージョンは1.23.2を使用
- [Gin](https://github.com/gin-gonic/gin)
    - Go言語のWebフレームワーク
- MySQL
    - 認証情報を MySQL データベースに保存しようと思います
    - `mysql`コマンドが使える状態にしておいてください
- Docker
    - Dockerを使ってMySQLを用意します
    - `docker`コマンドが使える状態にしておいてください
- [Postman](https://www.postman.com/downloads/)
    - エンドポイントの検証に使用します


## 手順 
始める前に、作成するAPIについて簡単に触れておきます。
1\. 「サインアップ」エンドポイント
- `/api/register`
    - ユーザー登録のパス
    - JWTを不要にし公開したままにする
    
2\. 「ログイン」エンドポイント
- `/api/login`
    - ログインに利用するパス
    - 「ユーザー名」と「パスワード」を受け取り、JWT を生成して返却

3\. 「ログイン後にしか呼び出しできない」エンドポイント
- `/api/admin/user`
    - JWT が必須

この3つのエンドポイントを作成していきます。

それでは始めてきましょう！

## セットアップ
```terminal:terminal
# 作業用のディレクトリを作成(go-authで進めていきます)
$ mkdir go-auth

# 作成したディレクトリに移動後、VScodeを起動
$ cd go-auth
$ code .
```

まずはGoモジュールの初期化を行うために、以下のコマンドを叩いてください。
```terminal
# 作成するモジュール名は「go-auth」でいきます
$ go mod init go-auth
```
次に必要なモジュールのインストールを行います。
```terminal:terminal
# Gin フレームワーク
$ go get -u github.com/gin-gonic/gin

# ORM ライブラリ
$ go get -u gorm.io/gorm

# JWT の認証と生成に使用するパッケージ
$ go get -u github.com/dgrijalva/jwt-go

# .env ファイルから環境変数を読み込む
$ go get -u github.com/joho/godotenv

# パスワード暗号化
$ go get -u golang.org/x/crypto
```
インストールが終えたら、main関数を作成し最初のエンドポイントを作っていきます！

## コーディング開始
### main関数の作成
`main.go`ファイルを作成したら、以下のコードを記述してください。
```golang:main.go
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    public := router.Group("/api")

    public.POST("/register", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "data": "register endpoint",
        })
    })

    router.Run(":8080")
}
```
では、以下のコマンドをVScode上のターミナルで叩いて、実行してみましょう。
```terminal:terminal
$ go run main.go

# 「Listening and serving HTTP on :8080」と出力されればOKです
```
ブラウザや、`curl`コマンドでアクセスしてJSONが返ってきたらOKです。
```terminal
$ curl -X POST http://localhost:8080/api/register
{"data":"register endpoint"}%
```

## 「サインアップ」エンドポイントの作成
登録処理の追加を行うため、`controllers`ディレクトリを作成します。
この中にController処理を実装するために`auth.go`ファイルを作成してください。
それでは`auth.go`ファイルにコードを書いていきましょう。
```golang:controllers/auth.go
package controllers

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func Register(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "data": "register endpoint.",
    })
}
```
次に、Register関数が`/register`エンドポイントと結びつくように、`main.go`ファイルを修正していきます。
```golang:main.go
package main

import (
    // controllersパッケージのインポート
    "go-auth/controllers"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    public := router.Group("/api")
  // Register関数の呼び出し
    public.POST("/register", controllers.Register)

    router.Run(":8080")
}
```
再度、Goを起動させ、curlコマンドあるいはブラウザでJSONを出力されるか確認しましょう。
```terminal
$ go run main.go

$ curl -X POST http://localhost:8080/api/register
{"data":"register endpoint."}%
```

### 「サインアップ」エンドポイントのバリデーションチェック
今回は、サインアップに「ユーザー名」＋「パスワード」のみ入力します。
その入力情報をチェックするためにGinのbindingというチェック機能を使用します。
```golang:controllers/auth.go
package controllers

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type RegisterInput struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Register(c *gin.Context) {
	var input RegisterInput

	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"data": "validated!",
	})
}
```
それではgoを起動させ、動作するか確認してきます。
まずは、「パスワード」のみの入力で検証していきましょう。
この検証をするにあたって `Postman`を使用します。
入力内容は以下の通りです。
![スクリーンショット 2024-11-05 22.32.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/f6aafd30-29c1-b295-5de8-15d48cf51ae7.png)
- メソッドを`POST`を指定し、`http://localhost:8080/api/register`とする
- `Body`の`row`にチェックを入れ、`JSON`を選択しましょう
- 以下のJSONが返ってくるか確認してください

![スクリーンショット 2024-11-05 22.37.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/02250322-5767-365a-a657-62eeb5016b1e.png)

次はユーザー名のみも同様に{"username":"<ユーザー名>"}だけを入れて、エラーがJSONで返ってくるか確かめます。
![スクリーンショット 2024-11-05 22.40.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/e9295c74-d6d4-0bd8-ffeb-7c139f88e699.png)

最後に「ユーザー名」と「パスワード」の両方を入れましょう。
![スクリーンショット 2024-11-05 22.42.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/be8ae3a5-8652-bf69-ea54-3489f2d9d593.png)
画像の通りのJSONが返ってきたらOKです！

### 認証情報を MySQL データベースに保存
Dockerを起動させ、MySQLを用意します。
以下のコマンドを叩いてDockerを起動させましょう。
```terminal
$ docker run --rm -p 3307:3306 -e MYSQL_ROOT_PASSWORD=dev -e MYSQL_USER=dev -e MYSQL_PASSWORD=dev -e MYSQL_DATABASE=dev -d mysql/mysql-server:5.7
```
簡単にコマンドの説明をすると、
- `docker run`：Dockerコンテナの実行を
- 環境変数の設定は以下の通りです
    - `-e MYSQL_USER=dev`：devというユーザーの作成
    - `-e MYSQL_PASSWORD=dev`：devユーザーのパスワードをdevに設定
    - `-e MYSQL_DATABASE=dev`：DB名をdevに設定
    - `-d`：バックグラウンドでコンテナが実行されるため、ターミナルを占有しません

起動させた後は、MySQLに接続していきます。以下のコマンドで接続を確認してください。
```terminal
$ mysql -h 127.0.0.1 -P 3307 -u dev -pdev dev
```
データベースへの接続処理と Model を格納するためのパッケージとコードを作成します。
それでは、`models`ディレクトリを作成し、その中に`setup.go`ファイルを作成してコードを書いていきましょう。
```golang:models/setup.go
package models

import (
	"fmt"
	"log"
	"os"

	"github.com/joho/godotenv"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB

func ConnectDataBase() {
	err := godotenv.Load()

	if err != nil {
		log.Fatalf("Error loading .env file")
	}

	dbUser := os.Getenv("DB_USER")
	dbPass := os.Getenv("DB_PASS")
	dbName := os.Getenv("DB_NAME")
	dbHost := os.Getenv("DB_HOST")
	dbPort := os.Getenv("DB_PORT")

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPass, dbHost, dbPort, dbName)

	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("Could not connect to the database", err)
	}

	// ここで適切なエンティティに対してマイグレーションを実行します。
	DB.AutoMigrate(&User{})
}
```
また、`.env`ファイルと`User`構造体の二つを用意する必要があるので作成していきましょう。
ルートディレクトリに`.env`ファイルを作成し以下のコードを書いてください。
```vim:.env
DB_DRIVER=mysql
DB_USER=dev
DB_PASS=dev
DB_NAME=dev
DB_HOST=localhost
DB_PORT=3307
```
次に、`models`ディレクトリに`user.go`ファイルを作成し`User`Modelを作成しましょう。
```golang:models/user.go
package models

import "gorm.io/gorm"

type User struct {
	gorm.Model
	Username string `gorm:"size:255;not null;unique" json:"username"`
	Password string `gorm:"size:255;not null;" json:"password"`
}
```
また、`main.go`ファイルにDB接続する処理を追加していきます。
```golang:main.go
package main

import (
	"go-auth/controllers"
	"go-auth/models"

	"github.com/gin-gonic/gin"
)

func main() {
	models.ConnectDataBase()

	router := gin.Default()

	public := router.Group("/api")

	public.POST("/register", controllers.Register)

	router.Run(":8080")
}
```
処理がかけたのでGoを起動させましょう。
その前に、`go mod tidy`を叩いて依存関係の整理をしておきましょう。

:::note 
`go mod tidy`はGoモジュールの依存関係を整えるためのコマンドです。
依存関係を追加した後や削除した後には、適宜このコマンドを実行しましょう！
:::
```terminal:terminal
# 使用するパッケージの追加処理
$ go mod tidy

$ go run main.go
```
起動できたら、以下のコマンドでMySQLに接続し、usersテーブルを表示させてみましょう。
```terminal:terminal
$ mysql -h 127.0.0.1 -P 3307 -u dev -pdev dev

mysql> show tables;
+---------------+
| Tables_in_dev |
+---------------+
| users         |
+---------------+
```
DBのマイグレーション処理が正しく動作し `users` テーブルが追加されたのことが確認できます。

### ユーザー情報の登録
サインアップ実装の最終段階をしていきましょう。
`users`テーブルへの登録ロジックを実装していきます。
```golang:models/user.go
package models

import (
	"strings"

	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	Username string `gorm:"size:255;not null;unique" json:"username"`
	Password string `gorm:"size:255;not null;" json:"password"`
}

// User オブジェクトをデータベースに保存する
func (u *User) Save() (*User, error) {
	err := DB.Create(u).Error
	if err != nil {
		return nil, err
	}
	return u, nil
}

// Userオブジェクトが保存される前に実行する
func (u *User) BeforeSave(*gorm.DB) error {
	// パスワードをハッシュ化する
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	u.Password = string(hashedPassword)

	// ユーザーネームを小文字に変換する
	u.Username = strings.ToLower(u.Username)

	return nil
}

// パスワードを空文字にして出力準備をする
func (u *User) PrepareOutput() *User {
	u.Password = ""
	return u
}
```
`auth.go`ファイルも修正していきます。
```controllers/auth.go
package controllers

import (
	"go-auth/models"
	"net/http"

	"github.com/gin-gonic/gin"
)

type RegisterInput struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Register(c *gin.Context) {
	var input RegisterInput

	// リクエストのJSONデータをRegisterInput構造体にバインドする
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// ユーザーオブジェクトを作成し、データベースに保存する
	user := &models.User{Username: input.Username, Password: input.Password}
	user, err := user.Save()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"data": user.PrepareOutput(),
	})
}
```
Saveメソッドも戻り値を正しく受け取るために、`user`変数をポインタ型 `(*models.User)` に変更しました。
Goを起動させ、検証していきましょう！
```terminal:terminal
$ go run main.go 
```
`Postman`を使用してPOSTリクエストを送ってみましょう。以下の画像の通りに進めてください。
![スクリーンショット 2024-11-07 21.37.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/422f5880-0d76-5b7e-17c0-e65d7ea43efd.png)
ステータスコード`200`が返ってきたことを確認した後、MySQLに接続し`users`テーブルにデータが追加されているか確認していきます。

```terminal
# MySQLに接続
$ mysql -h 127.0.0.1 -P 3307 -u dev -pdev dev

# SELECT文でusersテーブルのデータを確認
mysql> select * from users;
+----+-------------------------+-------------------------+------------+----------+--------------------------------------------------------------+
| id | created_at              | updated_at              | deleted_at | username | password                                                     |
+----+-------------------------+-------------------------+------------+----------+--------------------------------------------------------------+
|  4 | 2024-11-07 21:33:43.172 | 2024-11-07 21:33:43.172 | NULL       | fujifuji | $2a$10$IZpxktFgsKDLyDOkK/m1te8.DLAPh/XuFcBf5BO0ci4XRgKbJ.Ak6 |
+----+-------------------------+-------------------------+------------+----------+--------------------------------------------------------------+
```
データが登録され、パスワードがハッシュ化されていればOKです！

## 「ログイン」エンドポイントの作成
それでは、ログイン用のエンドポイントを作成していきましょう。
「ユーザー名」と「パスワード」を受け取り、DBにあるusersテーブルと照合を行う処理を書いていきます。
`main.go`ファイルから編集していきます。
```golang:main.go
package main

import (
	"go-auth/controllers"
	"go-auth/models"

	"github.com/gin-gonic/gin"
)

func main() {
	models.ConnectDataBase()

	router := gin.Default()

	public := router.Group("/api")

	public.POST("/register", controllers.Register)
 // /loginエンドポイントの追加
	public.POST("/login", controllers.Login)

	router.Run(":8080")
}
```
`auth.go`ファイルに`controllers.Login`メソッドの作成をしていきます。
```golang:controllers/auth.go
package controllers

import (
	"go-auth/models"
	"net/http"

	"github.com/gin-gonic/gin"
)

type RegisterInput struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Register(c *gin.Context) {
	var input RegisterInput

	// リクエストのJSONデータをRegisterInput構造体にバインドする
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// ユーザーオブジェクトを作成し、データベースに保存する
	user := &models.User{Username: input.Username, Password: input.Password}
	user, err := user.Save()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 成功した場合、ユーザー情報をレスポンスとして返す
	c.JSON(http.StatusOK, gin.H{
		"data": user.PrepareOutput(),
	})
}

type LoginInput struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Login(c *gin.Context) {
	var input LoginInput

	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	token, err := models.GenerateToken(input.Username, input.Password)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"token": token,
	})
}
```

`Controller` である`Login`関数から呼び出している `GenerateToken` 関数を `user.go` ファイルに追記します。

```golang:models/user.go
package models

import (
	"go-auth/utils/token"
	"strings"

	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	Username string `gorm:"size:255;not null;unique" json:"username"`
	Password string `gorm:"size:255;not null;" json:"password"`
}

// User オブジェクトをデータベースに保存する
func (u *User) Save() (*User, error) {
	err := DB.Create(u).Error
	if err != nil {
		return nil, err
	}
	return u, nil
}

// Userオブジェクトが保存される前に実行する
func (u *User) BeforeSave(*gorm.DB) error {
	// パスワードをハッシュ化する
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	u.Password = string(hashedPassword)

	// ユーザーネームを小文字に変換する
	u.Username = strings.ToLower(u.Username)

	return nil
}

// パスワードを空文字にして出力準備をする
func (u *User) PrepareOutput() *User {
	u.Password = ""
	return u
}

func GenerateToken(username string, password string) (string, error) {
	var user User

	err := DB.Where("username = ?", username).First(&user).Error

	if err != nil {
		return "", err
	}

	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))

	if err != nil {
		return "", err
	}

	token, err := token.GenerateToken(user.ID)

	if err != nil {
		return "", err
	}

	return token, nil
}
```

`token.go`ファイルを作成し、このファイルにトークンの操作する関数を作成していきます。
```terminal:terminal
$ mkdir -p ./utils/token

$ touch ./utils/token/token.go
```
以下の3つの関数を外部から呼び出せるようにします。
- `GenerateToken` 関数
- `TokenValid` 関数
- `ExtractTokenId` 関数

 `TokenValid` 関数と `ExtractTokenId` 関数の2つに関しては後ほど利用します。
 それでは`token.go`ファイルを編集していきましょう。
 ```golang:utils/token/token.go
package token

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

// 指定されたユーザーIDに基づいてJWTトークンを生成する
func GenerateToken(id uint) (string, error) {
	tokenLifespan, err := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN"))

	if err != nil {
		return "", err
	}

	claims := jwt.MapClaims{}
	claims["authorized"] = true
	claims["user_id"] = id
	claims["exp"] = time.Now().Add(time.Hour * time.Duration(tokenLifespan)).Unix()
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	return token.SignedString([]byte(os.Getenv("API_SECRET")))
}

func extractTokenString(c *gin.Context) string {
	bearToken := c.Request.Header.Get("Authorization")
	strArr := strings.Split(bearToken, " ")
	if len(strArr) == 2 {
		return strArr[1]
	}

	return ""
}

func parseToken(tokenString string) (*jwt.Token, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("There was an error")
		}
		return []byte(os.Getenv("API_SECRET")), nil
	})

	if err != nil {
		return nil, err
	}

	return token, nil
}

// トークンが有効かどうかを検証
func TokenValid(c *gin.Context) error {
	tokenString := extractTokenString(c)

	token, err := parseToken(tokenString)

	if err != nil {
		return err
	}

	if !token.Valid {
		return fmt.Errorf("Invalid token")
	}

	return nil
}

// トークンからユーザーIDを取得
func ExtractTokenId(c *gin.Context) (uint, error) {
	tokenString := extractTokenString(c)

	token, err := parseToken(tokenString)

	if err != nil {
		return 0, err
	}

	claims, ok := token.Claims.(jwt.MapClaims)

	if ok && token.Valid {
		userId, ok := claims["user_id"].(float64)

		if !ok {
			return 0, nil
		}

		return uint(userId), nil
	}

	return 0, nil
}
 ```
そして、コードに登場した`TOKEN_HOUR_LIFESPAN`と`API_SECRET`の2つの環境変数を`.env`ファイルに追加していきます。
```vim:.env
DB_DRIVER=mysql
DB_USER=dev
DB_PASS=dev
DB_NAME=dev
DB_HOST=localhost
DB_PORT=3307
// トークンの有効期限
TOKEN_HOUR_LIFESPAN=1
// JSONの署名に使用する秘密鍵
API_SECRET=go-auth-dev
```
では、Goを起動させてみましょう！
```terminal:terminal
# 使用するパッケージの追加処理(忘れないように👍)
$ go mod tidy
$ go run main.go
```

`Postman`でリクエストを投げて動作確認していきましょう。
まずは、**ユーザー名のみ**正しいパターン
![スクリーンショット 2024-11-07 23.19.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/d90c23e4-9f84-7791-e55b-398f451f2223.png)
以下のJSONが返ってくるか確認しましょう。
```json:JSON
"error": "crypto/bcrypt: hashedPassword is not the hash of the given password"
```
次は、**パスワードのみ**正しいパターン
![スクリーンショット 2024-11-07 23.25.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/61e147d1-c313-18a1-ef2f-cf0a0744388f.png)

```json:JSON
"error": "record not found"
```
ラストに、**ユーザー名・パスワード**ともに正しいパターン
![スクリーンショット 2024-11-07 23.25.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/d65b5eb4-7bf9-e5c7-286b-51f2966bc8a5.png)
ログインに成功した際に、トークンが生成されるようになりました。

## 認証用ミドルウェアの作成
Ginにはミドルウェアという機能があり、これを使用して`Controller` にリクエストが到達する前に処理を行うようにしていきます。
`middlerware`ディレクトリを作成し、その中に`middlerwares.go`ファイルを作成し、以下のように書いてください。
```golang:middlewares/middlerwares.go
package middlewares

import (
	"go-auth/utils/token"
	"net/http"

	"github.com/gin-gonic/gin"
)
// JwtAuthMiddleware はJWT認証を行うミドルウェアを返します
func JwtAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		err := token.TokenValid(c)

		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
			c.Abort()
			return
		}

		c.Next()
	}
}
```
`main.go`ファイルに新しい`/api/admin/user `エンドポイントの追加とこれに対するミドルウェアの設定を行います。
```golang:main.go
package main

import (
	"go-auth/controllers"
	middlewares "go-auth/middlerwares"
	"go-auth/models"

	"github.com/gin-gonic/gin"
)

func main() {
	models.ConnectDataBase()

	router := gin.Default()

	public := router.Group("/api")
    
	public.POST("/register", controllers.Register)
	public.POST("/login", controllers.Login)

	protected := router.Group("/api/admin")
   // JWT認証ミドルウェアを適用
	protected.Use(middlewares.JwtAuthMiddleware())
   // 認証されたユーザー情報を取得するルートを定義
	protected.GET("/user", controllers.CurrentUser)

	router.Run(":8080")
}
```
そして新たに追加した`CurrentUser` 関数を`auth.go`ファイルに書いていきます。
```golang:controllers/auth.go
package controllers

import (
	"go-auth/models"
	"go-auth/utils/token"
	"net/http"

	"github.com/gin-gonic/gin"
)

type RegisterInput struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Register(c *gin.Context) {
	var input RegisterInput

	// リクエストのJSONデータをRegisterInput構造体にバインドする
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// ユーザーオブジェクトを作成し、データベースに保存する
	user := &models.User{Username: input.Username, Password: input.Password}
	user, err := user.Save()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 成功した場合、ユーザー情報をレスポンスとして返す
	c.JSON(http.StatusOK, gin.H{
		"data": user.PrepareOutput(),
	})
}

type LoginInput struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Login(c *gin.Context) {
	var input LoginInput

	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	token, err := models.GenerateToken(input.Username, input.Password)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"token": token,
	})
}

func CurrentUser(c *gin.Context) {
   // トークンからユーザーIDを抽出する
	userId, err := token.ExtractTokenId(c)

	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
		return
	}

	var user models.User
   // ユーザーIDに基づいてユーザー情報をデータベースから取得する
	err = models.DB.First(&user, userId).Error

	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"data": user.PrepareOutput(),
	})
}
```
それでは、Go起動させて検証していきましょう！
ますは`/api/login`エンドポイントに対してログインを行い、トークンを取得していきます。
![スクリーンショット 2024-11-08 21.37.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/ea8c517f-6699-9b0f-e69d-4b3083129133.png)
取得したトークンは後で使用するので`Postman`で別タブを開いておきましょう。

次に、`/api/admin/user`エドポイントにGETリクエストを送ってみましょう。
まずはトークンを使用しないパターンを検証していきます。Postmanの設定は以下の画像の通りです。
- メソッドを`GET`にする
- `Headers`タブを開いて`Key`に`Authorization`、`Value`は空で大丈夫です

![スクリーンショット 2024-11-08 21.45.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/c729848a-256d-0c56-55c4-2e4bd23c0d3a.png)
エラーが返ってきたのでOKです！
今度は`Value`にトークンを設定してリクエストを送ってみましょう。
- `Value`に`Bearer <トークン>`を入力する
    - `Bearer`の後は半角スペースを入れてください 

![スクリーンショット 2024-11-08 21.49.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763692/64f01c31-09ac-1588-9f96-b3be835a0b8a.png)
うまくデータが取得できたらOKです！

## さいごに
今回はGo言語のGinを使い、簡単なログイン認証システムを作成しました。
エンドポイントの構築やJWTによる認証、MySQLデータベースとの連携方法を通して、Webアプリケーションの基本的な認証の仕組みについて学べたら幸いです。

## 参考

https://seefnasrul.medium.com/create-your-first-go-rest-api-with-jwt-authentication-in-gin-framework-dbe5bda72817

https://pkg.go.dev/github.com/dgrijalva/jwt-go