こんにちは、KOUKIです。
GolangのWebフレームワークである、fiberを使ってAPIを開発しています。
前回は、ユーザー認証まわりとログアウトの処理実装をしました。
今回は、Middlewareの実装方法を紹介します。
尚、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
前回
事前準備
フォルダ/ファイル作成
1 2 |
mkdir src/middleware touch src/middleware/auth.go |
作るもの
認証機能を作りたいと思います。エンドポイントは、次の通りです。
- 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/logout」と「/api/admin/user」へのリクエストにMiddlewareを挟む実装を行います。
Middlewareについて
ソフトウェアの世界では、「Middleware(ミドルウェア)」は様々な意味を持ちます。
しかし、この記事では、HTTPのリクエストからレスポンスまでの間に実行される任意の処理を指すことにします。
今回はCookie(認証)だけですが、公式サイトには色々なMiddlewareが紹介されています。
- BasicAuth
- Cache
- Compress
- CORS
- CSRF
- ETag
- Favicon
- FileSystem
- Limiter
- Logger
- Pprof
- Recover
- Proxy
- RequestID
- Session
- Timeout
LoggerとかTimeoutは便利そうですね。
以下の記事も参考になると思いますので、よかったらどうぞ。
Middlewareの実装
以下の処理は、auathController.goのUser関数からの抜粋です。Cookieから「jwt」をキーにToken情報を取得しています。ログイン状態か否かを判定しているわけですが、この処理をMiddleware化することで、汎用性の高いプログラムを実装することができそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 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": "認証がされていません", }) } |
Cookie取得処理
新しく作成したauth.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 |
// auth.go package middleware import ( "strconv" "github.com/dgrijalva/jwt-go" "github.com/gofiber/fiber/v2" ) func IsAuthenticate(ctx *fiber.Ctx) error { log.Println("Do Middleware") // 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 || !token.Valid { ctx.Status(fiber.StatusUnauthorized) // 401 return ctx.JSON(fiber.Map{ "message": "認証がされていません", }) } return ctx.Next() } |
ctx.Nextメソッドが実行されると、Middlewareの対象となるルートのメソッドが実行されます。例えば、「/api/admin/user」の場合は、User関数が実行されます。
UserID取得処理
User関数に実装していたUserIDを取得する処理もMiddlewareに移動しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// auth.go func GetUserID(ctx *fiber.Ctx) (uint, 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 { return 0, err } payload := token.Claims.(*jwt.StandardClaims) id, _ := strconv.Atoi(payload.Subject) return uint(id), nil } |
ルートの変更
ルートからMiddlewareを使えるように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// routes/routes.go package routes import ( "admin/src/controllers" "admin/src/middleware" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // GROUP api := app.Group("api") admin := api.Group("admin") // No Middleware admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) // Middleware adminAuthenticated := admin.Use(middleware.IsAuthenticate) adminAuthenticated.Post("logout", controllers.Logout) adminAuthenticated.Get("user", controllers.User) } |
Useメソッドにミドルウェア関数(IsAuthenticate)を渡して、adminAuthenticated変数を作成しました。そして、 Middlewarewの適用を許可するルートをadminAuthenticatedのメソッドから呼びだします。
User関数の修正
auathController.goの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 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/middleware" "admin/src/models" "log" "strconv" "time" "github.com/dgrijalva/jwt-go" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) ... func User(ctx *fiber.Ctx) error { log.Println("Do User") id, _ := middleware.GetUserID(ctx) // ユーザー検索 var user models.User database.DB.Where("id = ?", id).First(&user) return ctx.JSON(user) } |
Middlewareを挟んでいるので、だいぶスッキリしましたね。
ログアウト関数
ログアウト関数は特に修正する必要はありませんが、検証用に以下のログを仕込んでおきましょう。
1 2 3 4 5 |
func Logout(ctx *fiber.Ctx) error { log.Println("Do Logout") // cookieをクリアする ... } |
検証
早速検証してみましょう。
以下のコマンドで、コンテナを立ち上げます。
1 |
docker-compose up |
ログイン
ログインをします。
- URL:http://localhost:8000/api/admin/login
- 形式: POST
1 2 3 4 |
{ "email": "self@ne.jp", "password": "a" } |

User
ユーザー情報を取得します。
- URL:http://localhost:8000/api/admin/user
- 形式: GET

データを問題なく取得できたので、Middlewareは問題なく機能したようですね。ログからも確認できます。
1 2 |
backend_1 | 2021/06/08 21:56:09 Do Middleware backend_1 | 2021/06/08 21:56:09 Do User |
Middleware -> User関数の順に実行されてますね。
Logout
ログアウトも実行しましょう。
- URL: http://localhost:8000/api/admin/logout
- 形式: POST

OKですね。ログからも確認できます。
1 2 |
backend_1 | 2021/06/08 21:58:29 Do Middleware backend_1 | 2021/06/08 21:58:29 Do Logout |
もう一度、Userを呼び出してみましょう。認証が通らないはずです。

Greatですね、こいつぁ。Cookieを使うと結構便利です。
次回
次回は、プロファイルの更新処理を実装しましょう。
Go言語まとめ
ソースコード
ここまでのソースコードを以下に記載します。
auth.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 |
// auth.go package middleware import ( "strconv" "github.com/dgrijalva/jwt-go" "github.com/gofiber/fiber/v2" ) func IsAuthenticate(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 || !token.Valid { ctx.Status(fiber.StatusUnauthorized) // 401 return ctx.JSON(fiber.Map{ "message": "認証がされていません", }) } return ctx.Next() } func GetUserID(ctx *fiber.Ctx) (uint, 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 { return 0, err } payload := token.Claims.(*jwt.StandardClaims) id, _ := strconv.Atoi(payload.Subject) return uint(id), nil } |
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 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/middleware" "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 { id, _ := middleware.GetUserID(ctx) // ユーザー検索 var user models.User database.DB.Where("id = ?", id).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 23 24 |
// routes/routes.go package routes import ( "admin/src/controllers" "admin/src/middleware" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // GROUP api := app.Group("api") admin := api.Group("admin") // No Middleware admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) // Middleware adminAuthenticated := admin.Use(middleware.IsAuthenticate) adminAuthenticated.Post("logout", controllers.Logout) adminAuthenticated.Get("user", controllers.User) } |
コメントを残す
コメントを投稿するにはログインしてください。