🍪
【Go】JWT+Cookieによる認証
はじめに
現在携わっているプロジェクトで、JWTを使って認証を行う実装を行うかもしれず、どのように実装することができるのか調べたので、備忘録的にその方法を書いていこうと思います。
環境
- go version go1.24.0 darwin/arm64
実装
全体像
login.go
func (uh *UserHandler) Login(ctx echo.Context) error {
user := User{}
if err := ctx.Bind(&user); err != nil {
return ctx.JSON(http.StatusBadRequest, err)
}
loginUser := User{}
if err := uh.DB.Where("id=?", user.ID).Take(&loginUser).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ctx.JSON(http.StatusNotFound, err)
}
return ctx.JSON(http.StatusInternalServerError, err)
}
if err := bcrypt.CompareHashAndPassword([]byte(loginUser.Password), []byte(user.Password)); err != nil {
return ctx.JSON(http.StatusInternalServerError, err)
}
// JWTオブジェクトの作成
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": loginUser.ID,
"exp": time.Now().Add(time.Hour * 1).Unix(),
})
// JWTオブジェクトの作成
stringToken, err := token.SignedString([]byte(os.Getenv("SECRET")))
fmt.Println(stringToken)
if err != nil {
return ctx.JSON(http.StatusInternalServerError, err)
}
// Cookie設定
cookie := new(http.Cookie)
cookie.Name = os.Getenv("COOKIE_NAME")
cookie.Value = stringToken
cookie.Expires = time.Now().Add(10 * time.Hour)
cookie.HttpOnly = true
cookie.SameSite = http.SameSiteNoneMode
cookie.Path = "/"
cookie.Secure = true
ctx.SetCookie(cookie)
return ctx.JSON(http.StatusNoContent, nil)
}
JWTオブジェクトの作成
login.go
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": loginUser.ID,
"exp": time.Now().Add(time.Hour * 1).Unix(),
})
-
NewWithClaims
を使うことで、JWTオブジェクトを作成(JWTのヘッダーとペイロードを作成)-
SigningMethodHS256
は「HMAC-SHA256の署名アルゴリズム」を指す(ヘッダー) -
MapClaims
は「JWTに埋め込むデータを設定」(ペイロード)
-
JWTに署名して文字列を作成
login.go
stringToken, err := token.SignedString([]byte(os.Getenv("SECRET")))
fmt.Println(stringToken)
if err != nil {
return ctx.JSON(http.StatusInternalServerError, err)
}
-
SignedString
を使うことで署名して文字列化されたトークンを生成する-
NewWithClaims
で作成したヘッダーとペイロードをBase64エンコードする - エンコードされたヘッダーとペイロードを結合し、引数として渡した秘密鍵を用いて、HMAC-SHA256方式で署名する
-
ヘッダー.ペイロード.署名
という形式で文字列のトークンを生成する
-
Cookieの設定
login.go
cookie := new(http.Cookie)
cookie.Name = os.Getenv("COOKIE_NAME")
cookie.Value = stringToken
cookie.Expires = time.Now().Add(10 * time.Hour)
cookie.HttpOnly = true
cookie.SameSite = http.SameSiteNoneMode
cookie.Path = "/"
cookie.Secure = true
ctx.SetCookie(cookie)
-
Cookieの各種設定
- Name:Cookie自体の名前を指定
- Value:Cookieに保存する値を指定
- Expires:Cookieの有効期限を指定
- HttpOnly:JavaScriptからアクセスできないようにする(trueの場合)
- SameSite:クロスサイトリクエストの許可設定
- Path:Cookieが適用されるURLの範囲("/"の場合はサイト全体に適用)
- Secure:HTTPSのみで送信
ctx.SetCookie(cookie)
でcookieを送る
試しに
試しにCookie情報をチェックした上で、問題なければ「Hello, World!」を返すメソッドを定義
hello.go
func (uh *UserHandler) Hello(ctx echo.Context) error {
cookie, err := ctx.Cookie(os.Getenv("COOKIE_NAME"))
if err != nil {
if err == http.ErrNoCookie {
return ctx.JSON(http.StatusUnauthorized, err)
}
return ctx.JSON(http.StatusBadRequest, err)
}
token := cookie.Value
if token == "" {
return ctx.JSON(http.StatusUnauthorized, map[string]string{"error: ": "Invalid Token"})
}
return ctx.String(http.StatusOK, "Hello, World!")
}
-
ctx.Cookie(Cookie名)
でcookie情報を取得- cookie情報がない場合は401(Unauthorized)エラーとなる
- それ以外は400(BadRequest)エラーとなる
-
cookie.Value
でcookieの値を取得- valueが空文字だった場合は401(Unauthorized)エラーとなる
- 上記をクリアしたら、文字列「Hello, World!」を返す
まとめ
JWTとCookieを使ったユーザー認証に関する実装を学ぶことができました。
実際はミドルウェアに適用して、常時認証を行うのが一般的かと思うので、そこについても理解を深めていきたいと思います!
Discussion