Zenn
🍪

【Go】JWT+Cookieによる認証

2025/03/24に公開

はじめに

現在携わっているプロジェクトで、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

ログインするとコメントできます