こんにちは、KOUKIです。
GolangのWebフレームワークである、fiberを使ってAPIを開発しています。
前回は、JWTを使って、トークンの発行処理を実装しました。
今回は、ユーザー認証まわりとログアウトの処理実装をします。
尚、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
前回
作るもの
認証機能を作りたいと思います。エンドポイントは、次の通りです。
- POST /api/admin/register
- POST /api/admin/login
- POST /api/admin/logout
- GET /api/admin/user
- PUT /api/admin/users/info
- PUT /api/admin/users/password
今回は、「/api/admin/user」のユーザー認証、「/api/admin/logout」のログアウト処理を実装します。
ユーザー認証処理
前回、CookieにユーザーIDを保存する処理を実装しました。APIを利用するために、CookieからユーザーIDを取得し、そのIDをKeyにDB検索を行い、結果としてユーザー情報を返す処理を実装したいと思います。
ルートの追加
リクエスト先を追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
// routes/routes.go ... func Setup(app *fiber.App) { // GROUP ... // POST admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) // GET admin.Get("user", controllers.User) // 追加 } |
コントローラーの追加
コントローラーに、User関数を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/models" "strconv" "time" "github.com/dgrijalva/jwt-go" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) ... func User(ctx *fiber.Ctx) error { // cookieから情報を取得 cookie := ctx.Cookies("jwt") // token取得 token, err := jwt.ParseWithClaims( cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte("secret"), nil }, ) if err != nil { ctx.Status(fiber.StatusUnauthorized) // 401 return ctx.JSON(fiber.Map{ "message": "認証がされていません", }) } // useridを取得する payload := token.Claims.(*jwt.StandardClaims) // ユーザー検索 var user models.User database.DB.Where("id = ?", payload.Subject).First(&user) return ctx.JSON(user) } |
検証
検証してみましょう。以下のコマンドで、コンテナを立ち上げてください。
1 |
docker-compose up |
コンテナが立ち上がったら、以下のパラメータでリクエストを送ってみましょう。
- URL -> http://localhost:8000/api/admin/user
- 形式 -> GET

OKですね。
出力形式の変更
現在のユーザーデータの戻り値は、以下のようになっています。
1 2 3 4 5 6 7 8 |
{ "ID": 1, "FirstName": "self", "LastName": "note", "Email": "self@ne.jp", "Password": "XXX", "IsAmbassdor": false } |
実は、この出力は変更することが可能です。
1 2 3 4 5 6 7 8 9 10 11 12 |
// models/user.go package models import "golang.org/x/crypto/bcrypt type User struct { ID uint `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` Password []byte `json:"-"` IsAmbassdor bool `json:"-"` } |
「json:」は、Goの構造体を任意の形式でエンコーディングします。例えば、「json:”hoge”」と指定すると、呼び出し元では、keyがhogeの情報を取得できます。
呼び出し元に何も情報を返したくない場合は、「json:"-"
」を指定してください。
データを取得し直してみましょう。

1 2 3 4 5 6 |
{ "id": 1, "first_name": "self", "last_name": "note", "email": "self@ne.jp" } |
GORMでユニークキーの付け方
jsonタグでエンコードできることを学んだので、gormのfieldタグもついでに学習しましょう。
1 2 3 4 5 6 7 8 |
type User struct { ID uint `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email" gorm:"unique"` // ユニーク Password []byte `json:"-"` IsAmbassdor bool `json:"-"` } |
Emailをユニーク扱い(重複禁止)にしました。
動作確認のため、Register関数に以下の処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// controllers/auathController.go .... func Register(ctx *fiber.Ctx) error { ... // パスワードセット ... // ユーザー作成 result := database.DB.Create(&user) if result.Error != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "そのEmailは既に登録されています", }) } return ctx.JSON(user) } |
既に登録したEmailで、ユーザー登録をしてみましょう。
- URL: http://localhost:8000/api/admin/register
- 形式: POST
1 2 3 4 5 6 7 |
{ "first_name": "self", "last_name": "note", "email": "self@ne.jp", <<< これが重複キー "password": "a", "password_confirm": "a" } |

重複チェックは問題なく動きましたね^^
ログアウト
最後に、ログアウト処理を実装します。
ルートの追加
logoutへリクエストを送れるように、ルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// routes/routes.go ... func Setup(app *fiber.App) { ... // POST ... admin.Post("logout", controllers.Logout) // 追加 // GET ... } |
コントローラーを追加
auathController.goにLogout関数を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// controllers/auathController.go func Logout(ctx *fiber.Ctx) error { // cookieをクリアする cookie := fiber.Cookie{ Name: "jwt", Value: "", Expires: time.Now().Add(-time.Hour * 24), // -を指定 HTTPOnly: true, } ctx.Cookie(&cookie) return ctx.JSON(fiber.Map{ "message": "success", }) } |
Cookie情報を削除する処理を追加しました。
検証
以下のパラメータで、ログアウト処理の検証を行いましょう。
- URL: http://localhost:8000/api/admin/logout
- 形式: POST

ログアウトに成功したら、User情報を取得してみましょう。

認証エラーになりましたね。OKです。
次回
次回は、Midlewareの作成をしましょう。
Go言語まとめ
ソースコード
ここまでのソースコードを以下に記載します。
user.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// models/user.go package models import "golang.org/x/crypto/bcrypt" type User struct { ID uint `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email" gorm:"unique"` Password []byte `json:"-"` IsAmbassdor bool `json:"-"` } func (u *User) SetPassword(pwd string) { // ハッシュパスワードを作成 hashPwd, _ := bcrypt.GenerateFromPassword([]byte(pwd), 12) u.Password = hashPwd } func (u *User) ComparePassword(pwd string) error { return bcrypt.CompareHashAndPassword(u.Password, []byte(pwd)) } |
auathController.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/models" "strconv" "time" "github.com/dgrijalva/jwt-go" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) func Register(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } if data["password"] != data["password_confirm"] { ctx.Status(fiber.StatusBadRequest) // 400 return ctx.JSON(fiber.Map{ "message": "パスワードに誤りがあります", }) } // ハッシュパスワードを作成 pwd, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 12) user := models.User{ FirstName: data["first_name"], LastName: data["last_name"], Email: data["email"], Password: pwd, IsAmbassdor: false, } // パスワードセット user.SetPassword(data["password"]) // ユーザー作成 result := database.DB.Create(&user) if result.Error != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "そのEmailは既に登録されています", }) } return ctx.JSON(user) } func Login(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } var user models.User database.DB.Where("email = ?", data["email"]).First(&user) if user.ID == 0 { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // パスワードチェック err := user.ComparePassword(data["password"]) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // トークンの発行 payload := jwt.StandardClaims{ Subject: strconv.Itoa(int(user.ID)), ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), } token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, payload).SignedString([]byte("secret")) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // Cookieに保存 cookie := fiber.Cookie{ Name: "jwt", Value: token, Expires: time.Now().Add(time.Hour * 24), HTTPOnly: true, } ctx.Cookie(&cookie) return ctx.JSON(fiber.Map{ "message": "success", }) } func User(ctx *fiber.Ctx) error { // cookieから情報を取得 cookie := ctx.Cookies("jwt") // token取得 token, err := jwt.ParseWithClaims( cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte("secret"), nil }, ) if err != nil { ctx.Status(fiber.StatusUnauthorized) // 401 return ctx.JSON(fiber.Map{ "message": "認証がされていません", }) } // useridを取得する payload := token.Claims.(*jwt.StandardClaims) // ユーザー検索 var user models.User database.DB.Where("id = ?", payload.Subject).First(&user) return ctx.JSON(user) } func Logout(ctx *fiber.Ctx) error { // cookieをクリアする cookie := fiber.Cookie{ Name: "jwt", Value: "", Expires: time.Now().Add(-time.Hour * 24), // -を指定 HTTPOnly: true, } ctx.Cookie(&cookie) return ctx.JSON(fiber.Map{ "message": "success", }) } |
routes.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// routes/routes.go package routes import ( "admin/src/controllers" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // GROUP api := app.Group("api") admin := api.Group("admin") // POST admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) admin.Post("logout", controllers.Logout) // GET admin.Get("user", controllers.User) } |
コメントを残す
コメントを投稿するにはログインしてください。