ginでユーザーの認証を行う
はじめに
この記事ではユーザーの認証処理のうちのバックエンド側で行われる処理であるjwtトークンの発行方法について解説します。
目次
- そもそもjwtトークンって何?
- jwtトークン発行のプロセスの概要
- 実際のコード
そもそもjwtトークンって何?
JWTとは何か?
- jwtトークンとは一言で表すとセキュアな情報交換を実現するためのデジタルトークンです。
このjwtトークンを使用することでクライアント側で今どのユーザーがログインしているかの判断を行うことができたり、そのユーザーのAPIアクセスの認可を行うことができたりします。
また、jwtトークンは3つの部分から構成されます。- ヘッダー:使用するアルゴリズムとトークンのタイプ(通常はjwt)を含みます。
- ペイロード:トークンに含まれるクレーム(情報)を含みます。例えばユーザーIDや有効期限など。
- 署名:ヘッダーとペイロードを秘密鍵で署名したものです。これによってjwtトークン全体が暗号化されてトークンの改ざんを防げます。
実際のjwtトークンは以下のようになっています。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IlVzZXIxQGdtYWlsLmNvbSIsImV4cCI6MTczODEzOTI1Niwic3ViIjo4OX0.nllm4EckwZ8mOKlvxoo8aNb67PhYzlFFVbHoGXt3dDk
このトークンが先ほど伝えたヘッダーとペイロードと署名の3つの部分に分かれています。
jwtトークン発行のプロセスの概要
-
今回はjwtトークン発行のプロセスについてのみ解説します。本来はクライアント側でjwtトークンを使用する方法もありますがそれはまた次回の記事でお話しします。
SignUpの処理とかだと必要な工程は増えるのですが今回の記事の本質ではない部分なので今回はより本質に近いLogin処理のみを解説します
実際のコード
Login関数
- Login関数では主に下記の処理を行います。
- メールアドレスを基にしたユーザー検索
- パスワードが一致するか(ハッシュ化をしているのでそれに合わせて処理)
- ユーザーのIDとメールアドレスを基にjwtトークンを作成するCreateToken関数に渡す
auth_service.go
func (s *AuthService) Login(email string, password string) (*string, time.Duration, error) {
foundUser, err := s.repository.FindUserByEmail(email)
if err != nil {
return nil, 0, err
}
if foundUser.Password == nil {
return nil, 0, errors.New("パスワードが設定されていません")
}
err = bcrypt.CompareHashAndPassword([]byte(*foundUser.Password), []byte(password))
if err != nil {
return nil, 0, err
}
jwtToken, tokenExpiry, err := s.CreateToken(foundUser.ID, foundUser.Email)
if err != nil {
return nil, 0, err
}
return jwtToken, tokenExpiry, nil
}
-
メールアドレスを基にしたユーザー検索
- 以下のコードでメールアドレスを基にユーザーを検索して返却します。
auth_service.go
foundUser, err := s.repository.FindUserByEmail(email)
-
パスワードが一致するか(ハッシュ化をしているのでそれに合わせて処理)
- 以下のコードでパスワードが一致しているかを確認します。このプロジェクトではパスワードのハッシュ化を行っているのでそれに合わせて確認方法も少し変更しています。
auth_service.go
err = bcrypt.CompareHashAndPassword([]byte(*foundUser.Password), []byte(password))
-
ユーザーのIDとメールアドレスを基にjwtトークンを作成するCreateToken関数に渡す
- CreateToken関数ではjwtトークンの作成と暗号化が行われています。以下のコードで引き渡しが行われています。
- tokenExpiaryはのちほど使用します
auth_service.go
jwtToken, tokenExpiry, err := s.CreateToken(foundUser.ID, foundUser.Email)
CreateToken関数
- CreateToken関数では主に以下の処理を行います。
- jwtトークンの作成
- 秘密鍵を使用したjwtトークンの暗号化
auth_service.go
func (s *AuthService) CreateToken(userId uint, email string) (*string, time.Duration, error) {
var tokenExpiry time.Duration
tokenExpiry = time.Hour * 24 * 14 // 14日間
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": userId,
"email": email,
"exp": time.Now().Add(tokenExpiry).Unix(),
})
tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY")))
if err != nil {
return nil, 0, err
}
return &tokenString, tokenExpiry, nil
}
-
jwtトークンの作成
- 以下のコードでjwtトークンのインスタンスを作成しています。ここではまだ暗号化されていません。
- 今回は「ユーザーID」と「メールアドレス」と「有効期限」をjwtトークンに含めていますが必ずしもこれらのみで構成されるわけではなく、ほかにもいくつかのパラメータが存在します。
auth_service.go
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": userId,
"email": email,
"exp": time.Now().Add(tokenExpiry).Unix(),
})
-
jwtトークンの暗号化
- 以下のコードで暗号化を行います。
- ここでは秘密鍵を使用して暗号化を行います。この秘密鍵は何でもよいですが一般的には文字列で複雑で長い文字列であることが推奨されます。
auth_service.go
tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY")))
jwtトークンをクッキーにセットする
- 以下のコードでjwtトークンをクッキーにセットします。
auth_repository.go
ctx.SetCookie("jwt-token", *jwtToken, int(tokenExpiry.Seconds()), "/", "localhost", false, true)
- ここでSetCookie関数の各引数に関して説明します。
-
"jwt-token":
- クッキーの名前です。ここでは[jwt-token]としています。
-
*jwtToken:
- クッキーの値です。これは生成されたjwtTokenです。
-
int(tokenExpiry.Seconds()):
- クッキーの有効期限です。秒単位で管理することができ、このためにtokenExpiaryをLogin関数から返却しています。
-
"/":
- クッキーが適用されるパスです。ここではルートパスが使用されているので指定されたドメイン(次の引数で説明しますがここでは"localhost"のこと)内のすべてのパスに対してクッキーが送信されることを表しています。
-
"localhost":
- クッキーが送信されるドメインです。ここでは「localhost」が指定されています。
-
false:
- セキュアフラグ。このフラグが true の場合、クッキーはHTTPS接続でのみ送信されます。ここでは false に設定されています。
-
true:
- HttpOnlyフラグ。このフラグが true の場合、クッキーはJavaScriptからアクセスできません。セキュリティを向上させるために使われます。
まとめ
今回はjwtトークンの発行方法についての記事を書きました。これと対をなすjwtトークンを用いたAPIの認証やユーザーの取得についても別の記事で取り上げます。