こんにちは。KOUKIです。
前回に引き続き、ユーザー認証アプリの実装を行います。本記事は、主にバックエンドで以下の処理について書きます。
. リクエストデータフォーマットの変更
. パスワードリセット依頼機能
. メール送信機能
. パスワードリセット機能
作業を始める前に、コンテナを起動しておきましょう。
1 2 |
// コンテナの起動 docker-compose up |
<目次>
前回
補足:ホットリロードについて
本記事の動作環境では、Mac&reflexツールを使っているため、コンテナを立ち上げなおさなくとも修正内容が自動的に反映される環境(ホットリロード)ですが、Windows環境ではホットリロードが動作しないため、コードの修正があるたびにコンテナを立ち上げなおしてください。
フォーマットの変更
リクエスト/レスポンスデータは、「FirstName」のようにUser構造体のパラメータ名になっていますが、jsonタグを使うと変更することが可能です。
1 2 3 4 5 6 7 8 9 10 11 12 |
// 現在 { "ID": 1, "CreatedAt": "2021-02-04T02:25:19.197858265Z", "UpdatedAt": "2021-02-04T02:25:19.197858265Z", "DeletedAt": null, "FirstName": "self", "LastName": "note", "Email": "selfnote@yahoo.co.jp", "Password": "JDJhJDE0JE9IbVdLODd6YXV5YmRobERoZ29KNC5zQU1ZTEZLc2w4cW5CU2VJQWM2NDlrU2VldVMwU2FX" } |
user.goファイルを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// user.go package models import "gorm.io/gorm" type User struct { gorm.Model FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email" gorm:"unique"` Password []byte `json:"-"` // -を指定すると非表示にできる } |
「http://localhost/api/user」にGetリクエストを送ってみましょう。

1 2 3 4 5 6 7 8 9 |
{ "ID": 1, "CreatedAt": "2021-02-02T08:49:26.035Z", "UpdatedAt": "2021-02-02T08:49:26.035Z", "DeletedAt": null, "first_name": "self", "last_name": "note", "email": "selfnote@yahoo.co.jp" } |
jsonタグを指定したところは、フォーマットが変更されたことがわかりますね。パスワードも非表示になっています。
パスワードリセット依頼機能の作成
次は、パスワードリセット依頼機能を作成します。
よくログイン画面などでみる「パスワードを忘れた場合」からパスワードリセット依頼をするあの機能です。

コントローラーの作成
パスワードリセット用のコントローラーを作成します。
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 |
// forgotController.go package controllers import ( "auth-api/database" "auth-api/models" "math/rand" "github.com/gofiber/fiber/v2" ) func Forgot(c *fiber.Ctx) error { var data map[string]string // リクエストデータをパースする if err := c.BodyParser(&data); err != nil { return err } token := RandStringRunes(12) passwordReset := models.PasswordReset{ Email: data["email"], Token: token, } // DBに保存 database.DB.Create(&passwordReset) return c.JSON(fiber.Map{ "message": "SUCCESS", }) } // ランダム文字列を返す関数 func RandStringRunes(n int) string { var lettersRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") b := make([]rune, n) for i := range b { b[i] = lettersRunes[rand.Intn(len(lettersRunes))] } return string(b) } |
PasswordReset構造体は、これから作成します。
RandStringRunes関数は、簡易Tokenを作成するユーティリティ関数です。
モデルの作成
パスワードリセット用のモデルを作成します。
1 2 3 4 5 6 7 8 9 |
// passwordReset.go package models type PasswordReset struct { ID uint Email string Token string } |
このモデルをAutoMigrateに含めます。
1 2 3 4 5 6 7 8 9 10 |
// connect.go package database ... func Connect() { ... connection.AutoMigrate(&models.User{}, &models.PasswordReset{}) // 追加 } |
これで、PasswordResetのデータベーススキーマーが作成されるはずです。「http://localhost:8888/sql.php?server=1&db=go_auth&table=password_resets&pos=0」にアクセスすると確認できます。

ルートの追加
routers.goにForgot関数を呼びだすパスを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// routes.go package routes import ( "auth-api/controllers" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { app.Post("/api/register", controllers.Register) app.Post("/api/login", controllers.Login) app.Get("/api/user", controllers.User) app.Get("/api/logout", controllers.Logout) app.Post("/api/forgot", controllers.Forgot) // 追加 } |
動作確認
以下のパラメータで、POSTリクエストを送信してください。
URL: http://localhost/api/forgot
形式: POST
1 2 3 |
{ "email": "selfnote@yahoo.co.jp" } |


OKですね。
「http://localhost:8888/sql.php?server=1&db=go_auth&table=password_resets&pos=0」にアクセスして、データが格納されているか確認しましょう。

MailHogでメール送信
以前、Go言語でメール送信ができなかったときに学んだMailHogというオープンソースを使って、メール送信機能を実装してみましょう。
「http://localhost:8025/」にアクセスするとMailHogのインターフェースを確認できます。

Goの標準パッケージであるnet/smtpパッケージを使って、メール送信機能を作成しましょう。
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 |
// forgotController.go package controllers import ( "auth-api/database" "auth-api/models" "fmt" "math/rand" "net/smtp" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) ... func Forgot(c *fiber.Ctx) error { ... // DBに保存 database.DB.Create(&passwordReset) // SMTPメール送信 from := "selfnote-owner@yahoo.co.jp" to := []string{ data["email"], } sendFrom := fmt.Sprintf("From: %s\n", from) subject := fmt.Sprintf("Subject; %s\n", "Password Reset") mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" // Vue.jsのアドレス url := "http://localhost:8080/reset/" + token message := fmt.Sprintf("Click <a href=\"%s\">here</a> to reset password!", url) err := smtp.SendMail( "smtp:1025", // コンテナサービス名+port nil, from, to, []byte(sendFrom+subject+mime+message), ) if err != nil { return err } return c.JSON(fiber.Map{ "message": "SUCCESS", }) } |
smtpパッケージのSendMailを使って、メールを飛ばしています。その第一引数に指定している「”smtp:1025″」は、MailHogコンテナのサービス名です。
先ほどと同様に「http://localhost/api/forgot」に対して、リクエストを送ってみましょう。リクエストが成功すれば、メールが飛ぶはずです。


OKですね。
Plain text文だとURLとTokenが確認できます。このTokenは、パスワードリセット機能で使用します。
Click <a href="
http://localhost:8080/reset/hTHctcuAxhxK
">here</a> to reset password!
パスワードリセット機能
次は、パスワードをリセットする機能を実装します。
Reset関数の実装
コントローラーとして、Reset関数を定義します。
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 |
// forgotController.go package controllers ... func Reset(c *fiber.Ctx) error { var data map[string]string // リクエストデータをパースする if err := c.BodyParser(&data); err != nil { return err } // パスワードチェック if data["password"] != data["password_confirm"] { c.Status(400) return c.JSON(fiber.Map{ "message": "Passwords do not match!", }) } var passwordReset = models.PasswordReset{} // JWT Tokenからデータを取得 err := database.DB.Where("token = ?", data["token"]).Last(&passwordReset) if err.Error != nil { c.Status(400) return c.JSON(fiber.Map{ "message": "Invalid token!", }) } // パスワードをエンコード password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14) database.DB.Model(&models.User{}).Where("email = ?", passwordReset.Email).Update("password", password) return c.JSON(fiber.Map{ "message": "SUCCESS", }) } |
やっていることは、パスワードの一致確認、Tokenの存在チェック、パスワードのエンコード(DBに平文のまま格納するわけにはいかないため)くらいの簡単な処理です。
ルートの追加
routers.goからReset関数を呼び出せるようにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
// routes.go package routes ... func Setup(app *fiber.App) { app.Post("/api/register", controllers.Register) app.Post("/api/login", controllers.Login) app.Get("/api/user", controllers.User) app.Get("/api/logout", controllers.Logout) app.Post("/api/forgot", controllers.Forgot) app.Post("/api/reset", controllers.Reset) // 追加 } |
動作確認
現在、DBには「hTHctcuAxhxK
」のTokenが格納されているので、これを使って動作確認をしましょう。
リクエスト先及びデータは、以下です。
URL: http://localhost/api/reset
形式: POST
1 2 3 4 5 |
{ "token": "hTHctcuAxhxK ", "password": "abc", "password_confirm": "abc" } |


パスワードが変更できました。
パスワードの変更を確認するために、以下のリクエスト先にデータを送ってログインできるか確認してみましょう。
URL: http://localhost/api/reset
形式: POST
1 2 3 4 |
{ "email": "selfnote@yahoo.co.jp", "password": "abc" } |


これでバックエンド側の処理は完了です。難しい所があったかもしれませんが、慣れれば呼吸レベルでできるようになります
次回
次回からは、Vue.jsを使ったフロントエンド開発を行います。
コメントを残す
コメントを投稿するにはログインしてください。