4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

【Go】JWT認証をGinで必要最低限の実装をした

Last updated at Posted at 2024-01-24

やりたいこと

Ginでユーザーログイン機能を実装し、ログインユーザーだけがアクセス可能なエンドポイントをJWT認証を使って実装したい。

JWT認証の仕組み

JWTとは

JWT認証の仕組み

Ginで実装してみる

main.go

main.go
import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)

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

	r.POST("/login", loginHandler)

	authGroup := r.Group("/auth")
	authGroup.Use(authMiddleware)
	authGroup.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "you are authorized"})
	})

	r.Run(":8080")
}
  • POST /login:ログイン時にアクセスするエンドポイント
  • GET /auth:ログインユーザーしかアクセスできない

/authが付くエンドポイントをグループ化し、middlewareを用いて、JWT認証できたユーザーのみアクセスできるようにする。

User構造体を定義

type User struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

var validUser = User{
	Username: "test",
	Password: "testpass",
}

loginHandler

func loginHandler(c *gin.Context) {

	var inputUser User

	// リクエストからユーザー情報を取得
	if err := c.BindJSON(&inputUser); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// ユーザー情報の検証
	if inputUser.Username != validUser.Username || inputUser.Password != validUser.Password {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user"})
		return
	}

	// トークンの発行(ヘッダー・ペイロード)
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"username": inputUser.Username,
		"exp":      time.Now().Add(time.Hour * 24).Unix(),
	})
	tokenString, err := token.SignedString([]byte(SECRET_KEY))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "error signing token"})
		return
	}

	// ヘッダーにトークンをセット
	c.Header("Authorization", tokenString)
	c.JSON(http.StatusOK, gin.H{"message": "login success"})
}

リクエストBodyからUser構造体へバインドし、UsernamePasswordが一致する登録ユーザーが存在するかを検証します。今回は面倒なのでDBを使わずグローバル変数で定義しています。

その後JWTトークンを発行し、HTTP HeaderのAuthorizationにセットしてレスポンスを返します。

const SECRET_KEY = "SECRET"

署名に使う秘密鍵はこんな感じで定義しておきます。

authMiddleware

func authMiddleware(c *gin.Context) {
	// Authorizationヘッダーからトークンを取得
	tokenString := c.GetHeader("Authorization")

	// トークンの検証
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		return []byte(SECRET_KEY), nil
	})
	if err != nil || !token.Valid {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
		c.Abort()
		return
	}

	c.Next()
}

リクエストのHTTP HeaderのAuthorizationからJWTトークンを取得します。
その後、jwt.Parseを用いてJWTトークンを検証します。ここのミソはDBにアクセスしてユーザー情報を照合する必要がないことだと思います。JWTのシグネチャをデコードしてペイロードと一致すれば有効なトークンであることが保証されているのが理由です。

参考

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?