We all know we need to use strong passwords for every account we create on the internet. However, remembering the individual passwords we use on different websites isn’t that easy.

Most users who use unique passwords across different services are more likely to forget their passwords. To improve the user experience of our application, it’s crucial to add a password reset feature where users who forget their passwords can easily reset them.

This article will teach you how to add a secure forgot/reset password feature to a Golang RESTful API application.

Build Golang, GORM, PostgreSQL RESTful API Series:

  1. How to Setup Golang GORM RESTful API Project with Postgres
  2. API with Golang + GORM + PostgreSQL: Access & Refresh Tokens
  3. Golang and GORM – User Registration and Email Verification
  4. Forgot/Reset Passwords in Golang with HTML Email
  5. Build a RESTful CRUD API with Golang

More practice:

Forgot-Reset Passwords in Golang with SMTP HTML Email

What you will learn

  • How to generate the HTML Email template
  • How to send SMTP emails with Gomail
  • How to send the password reset link to the user’s email address
  • How to reset the user’s password

Forgot Password and Password Reset Flow

The forgot/reset password workflow can be achieved in many ways depending on how usable and secure your application is.

In this article, we’ll implement a standard forgot/reset password design. Below are the Golang endpoints included in this workflow.

HTTP METHODROUTEDESCRIPTION
POST/api/auth/forgotpasswordTo request a reset link
PATCH/api/auth/resetpassword/:resetTokenTo reset the password

To make your life earlier, copy the JSON Postman collection I used in testing the Golang API and import it into your Postman software.

-Here, the user provides the email address and makes a POST request to the /api/auth/forgotpassword endpoint to request a password reset link.

The Golang server validates the request, checks the database to see if a user with that email address exists, generates the password reset code and the HTML template, and sends the SMTP email to the user.

golang, gorm, smtp, and template engine send forgot password link

In the workflow, the user will be redirected to the frontend application upon clicking on the “Reset Password” button in the email. But since there is no frontend application, you need to copy the reset code and manually make the request to the Golang API.

golang, gorm, smtp, and template engine open email box

-Provide the new password and make a PATCH request with the reset code to the /api/auth/resetpassword/:resetToken endpoint.

The Golang server will then validate the password reset code and update the password data in the database.

golang, gorm, smtp, and template engine reset the password

-After resetting the password, provide the new password and make a request to the Golang server to obtain a new JSON Web Token.

golang, gorm, smtp, and template engine login with the new password

Step 1 – Create/Update the Database Models

Now create/update the models/user.model.go file to include the ResetPasswordInput and ForgotPasswordInput structs.

Gin Gonic will use these two structs to validate the request bodies to ensure that the user provides the required data.

models/user.model.go


package models

import (
	"time"

	"github.com/google/uuid"
)

type User struct {
	ID                 uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primary_key"`
	Name               string    `gorm:"type:varchar(255);not null"`
	Email              string    `gorm:"uniqueIndex;not null"`
	Password           string    `gorm:"not null"`
	Role               string    `gorm:"type:varchar(255);not null"`
	Provider           string    `gorm:"not null"`
	Photo              string    `gorm:"not null"`
	VerificationCode   string
	PasswordResetToken string
	PasswordResetAt    time.Time
	Verified           bool      `gorm:"not null"`
	CreatedAt          time.Time `gorm:"not null"`
	UpdatedAt          time.Time `gorm:"not null"`
}

type SignUpInput struct {
	Name            string `json:"name" binding:"required"`
	Email           string `json:"email" binding:"required"`
	Password        string `json:"password" binding:"required,min=8"`
	PasswordConfirm string `json:"passwordConfirm" binding:"required"`
	Photo           string `json:"photo" binding:"required"`
}

type SignInInput struct {
	Email    string `json:"email"  binding:"required"`
	Password string `json:"password"  binding:"required"`
}

type UserResponse struct {
	ID        uuid.UUID `json:"id,omitempty"`
	Name      string    `json:"name,omitempty"`
	Email     string    `json:"email,omitempty"`
	Role      string    `json:"role,omitempty"`
	Photo     string    `json:"photo,omitempty"`
	Provider  string    `json:"provider"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// ? ForgotPasswordInput struct
type ForgotPasswordInput struct {
	Email string `json:"email" binding:"required"`
}

// ? ResetPasswordInput struct
type ResetPasswordInput struct {
	Password        string `json:"password" binding:"required"`
	PasswordConfirm string `json:"passwordConfirm" binding:"required"`
}


The User struct represents the SQL table in the database. When the user makes a password reset request to the Golang API, the server will generate the reset token, and store the hashed code in the PasswordResetToken column in the database before sending the unhashed code to the user’s email address.

For security reasons, the password reset code will have an expiry time of 15 minutes. Within this period, the user is expected to complete the password reset process else a new reset link has to be requested since the old one will be deleted from the database.

Step 2 – Create an SMTP Account

In this example, we will use Mailtrap to capture the development emails so replace the SMTP credentials in the environment variables file with real SMTP credentials provided by services like Sendinblue, MailGun, SendGrid, and more when you are deploying the application to production.

Follow these steps to create the Mailtrap account:

Step 1: Create an account on Mailtrap if you don’t already have one.

Step 2: Sign into the account and click on the Add Inbox button to create a new mailbox.

Next, click on the gear icon to display the SMTP credentials.

mailtrap fastapi python click settings

Step 3: Click on the Show Credentials dropdown to show the SMTP and POP3 credentials.

mailtrap fastapi python smtp credentials_1

Step 4: Add the SMTP credentials to the environment variables file.

app.env


POSTGRES_HOST=127.0.0.1
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password123
POSTGRES_DB=golang-gorm
POSTGRES_PORT=6500

PORT=8000
CLIENT_ORIGIN=http://localhost:3000

EMAIL_FROM=admin@admin.com
SMTP_HOST=smtp.mailtrap.io
SMTP_USER=
SMTP_PASS=
SMTP_PORT=587

TOKEN_EXPIRED_IN=60m
TOKEN_MAXAGE=60

TOKEN_SECRET=my-ultra-secure-json-web-token-string

Step 5: Update the initializers/loadEnv.go file with the variables.

initializers/loadEnv.go


package initializers

import (
	"time"

	"github.com/spf13/viper"
)

type Config struct {
	DBHost         string `mapstructure:"POSTGRES_HOST"`
	DBUserName     string `mapstructure:"POSTGRES_USER"`
	DBUserPassword string `mapstructure:"POSTGRES_PASSWORD"`
	DBName         string `mapstructure:"POSTGRES_DB"`
	DBPort         string `mapstructure:"POSTGRES_PORT"`
	ServerPort     string `mapstructure:"PORT"`

	ClientOrigin string `mapstructure:"CLIENT_ORIGIN"`

	TokenSecret    string        `mapstructure:"TOKEN_SECRET"`
	TokenExpiresIn time.Duration `mapstructure:"TOKEN_EXPIRED_IN"`
	TokenMaxAge    int           `mapstructure:"TOKEN_MAXAGE"`

	EmailFrom string `mapstructure:"EMAIL_FROM"`
	SMTPHost  string `mapstructure:"SMTP_HOST"`
	SMTPPass  string `mapstructure:"SMTP_PASS"`
	SMTPPort  int    `mapstructure:"SMTP_PORT"`
	SMTPUser  string `mapstructure:"SMTP_USER"`
}

func LoadConfig(path string) (config Config, err error) {
	viper.AddConfigPath(path)
	viper.SetConfigType("env")
	viper.SetConfigName("app")

	viper.AutomaticEnv()

	err = viper.ReadInConfig()
	if err != nil {
		return
	}

	err = viper.Unmarshal(&config)
	return
}


Step 3 – Setup the HTML Templates

In this section, you will set up the HTML email template. Handling HTML templates can be a bit tedious, luckily for us, the Golang community has created an html/template package that implements data-driven templates for generating HTML outputs that are safe from code injection.

Add the Email Template CSS

Just like in most web applications, we declare the stylesheet in a separate file and reuse it in any HTML file.

The Golang html/template package provides a simple syntax to create sub-templates that can be reused across other templates.

To declare a sub-template, we use this syntax:

{{define "sub-template"}}content{{end}}

Now create a templates/styles.html file and add the following CSS code:

templates/styles.html


{{define "styles"}}
<style>
  /* -------------------------------------
          GLOBAL RESETS
      ------------------------------------- */

  /*All the styling goes here*/

  img {
    border: none;
    -ms-interpolation-mode: bicubic;
    max-width: 100%;
  }

  body {
    background-color: #f6f6f6;
    font-family: sans-serif;
    -webkit-font-smoothing: antialiased;
    font-size: 14px;
    line-height: 1.4;
    margin: 0;
    padding: 0;
    -ms-text-size-adjust: 100%;
    -webkit-text-size-adjust: 100%;
  }

  table {
    border-collapse: separate;
    mso-table-lspace: 0pt;
    mso-table-rspace: 0pt;
    width: 100%;
  }
  table td {
    font-family: sans-serif;
    font-size: 14px;
    vertical-align: top;
  }

  /* -------------------------------------
          BODY & CONTAINER
      ------------------------------------- */

  .body {
    background-color: #f6f6f6;
    width: 100%;
  }

  /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
  .container {
    display: block;
    margin: 0 auto !important;
    /* makes it centered */
    max-width: 580px;
    padding: 10px;
    width: 580px;
  }

  /* This should also be a block element, so that it will fill 100% of the .container */
  .content {
    box-sizing: border-box;
    display: block;
    margin: 0 auto;
    max-width: 580px;
    padding: 10px;
  }

  /* -------------------------------------
          HEADER, FOOTER, MAIN
      ------------------------------------- */
  .main {
    background: #ffffff;
    border-radius: 3px;
    width: 100%;
  }

  .wrapper {
    box-sizing: border-box;
    padding: 20px;
  }

  .content-block {
    padding-bottom: 10px;
    padding-top: 10px;
  }

  .footer {
    clear: both;
    margin-top: 10px;
    text-align: center;
    width: 100%;
  }
  .footer td,
  .footer p,
  .footer span,
  .footer a {
    color: #999999;
    font-size: 12px;
    text-align: center;
  }

  /* -------------------------------------
          TYPOGRAPHY
      ------------------------------------- */
  h1,
  h2,
  h3,
  h4 {
    color: #000000;
    font-family: sans-serif;
    font-weight: 400;
    line-height: 1.4;
    margin: 0;
    margin-bottom: 30px;
  }

  h1 {
    font-size: 35px;
    font-weight: 300;
    text-align: center;
    text-transform: capitalize;
  }

  p,
  ul,
  ol {
    font-family: sans-serif;
    font-size: 14px;
    font-weight: normal;
    margin: 0;
    margin-bottom: 15px;
  }
  p li,
  ul li,
  ol li {
    list-style-position: inside;
    margin-left: 5px;
  }

  a {
    color: #3498db;
    text-decoration: underline;
  }

  /* -------------------------------------
          BUTTONS
      ------------------------------------- */
  .btn {
    box-sizing: border-box;
    width: 100%;
  }
  .btn > tbody > tr > td {
    padding-bottom: 15px;
  }
  .btn table {
    width: auto;
  }
  .btn table td {
    background-color: #ffffff;
    border-radius: 5px;
    text-align: center;
  }
  .btn a {
    background-color: #ffffff;
    border: solid 1px #3498db;
    border-radius: 5px;
    box-sizing: border-box;
    color: #3498db;
    cursor: pointer;
    display: inline-block;
    font-size: 14px;
    font-weight: bold;
    margin: 0;
    padding: 12px 25px;
    text-decoration: none;
    text-transform: capitalize;
  }

  .btn-primary table td {
    background-color: #3498db;
  }

  .btn-primary a {
    background-color: #3498db;
    border-color: #3498db;
    color: #ffffff;
  }

  /* -------------------------------------
          OTHER STYLES THAT MIGHT BE USEFUL
      ------------------------------------- */
  .last {
    margin-bottom: 0;
  }

  .first {
    margin-top: 0;
  }

  .align-center {
    text-align: center;
  }

  .align-right {
    text-align: right;
  }

  .align-left {
    text-align: left;
  }

  .clear {
    clear: both;
  }

  .mt0 {
    margin-top: 0;
  }

  .mb0 {
    margin-bottom: 0;
  }

  .preheader {
    color: transparent;
    display: none;
    height: 0;
    max-height: 0;
    max-width: 0;
    opacity: 0;
    overflow: hidden;
    mso-hide: all;
    visibility: hidden;
    width: 0;
  }

  .powered-by a {
    text-decoration: none;
  }

  hr {
    border: 0;
    border-bottom: 1px solid #f6f6f6;
    margin: 20px 0;
  }

  /* -------------------------------------
          RESPONSIVE AND MOBILE FRIENDLY STYLES
      ------------------------------------- */
  @media only screen and (max-width: 620px) {
    table.body h1 {
      font-size: 28px !important;
      margin-bottom: 10px !important;
    }
    table.body p,
    table.body ul,
    table.body ol,
    table.body td,
    table.body span,
    table.body a {
      font-size: 16px !important;
    }
    table.body .wrapper,
    table.body .article {
      padding: 10px !important;
    }
    table.body .content {
      padding: 0 !important;
    }
    table.body .container {
      padding: 0 !important;
      width: 100% !important;
    }
    table.body .main {
      border-left-width: 0 !important;
      border-radius: 0 !important;
      border-right-width: 0 !important;
    }
    table.body .btn table {
      width: 100% !important;
    }
    table.body .btn a {
      width: 100% !important;
    }
    table.body .img-responsive {
      height: auto !important;
      max-width: 100% !important;
      width: auto !important;
    }
  }

  /* -------------------------------------
          PRESERVE THESE STYLES IN THE HEAD
      ------------------------------------- */
  @media all {
    .ExternalClass {
      width: 100%;
    }
    .ExternalClass,
    .ExternalClass p,
    .ExternalClass span,
    .ExternalClass font,
    .ExternalClass td,
    .ExternalClass div {
      line-height: 100%;
    }
    .apple-link a {
      color: inherit !important;
      font-family: inherit !important;
      font-size: inherit !important;
      font-weight: inherit !important;
      line-height: inherit !important;
      text-decoration: none !important;
    }
    #MessageViewBody a {
      color: inherit;
      text-decoration: none;
      font-size: inherit;
      font-family: inherit;
      font-weight: inherit;
      line-height: inherit;
    }
    .btn-primary table td:hover {
      background-color: #34495e !important;
    }
    .btn-primary a:hover {
      background-color: #34495e !important;
      border-color: #34495e !important;
    }
  }
</style>
{{end}}

Add the Password Reset HTML Template

Next, let’s create the templates/resetPassword.html file and include the CSS file.

templates/resetPassword.html


<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    {{template "styles" .}}
    <title>{{ .Subject}}</title>
  </head>
  <body>
    <table
      role="presentation"
      border="0"
      cellpadding="0"
      cellspacing="0"
      class="body"
    >
      <tr>
        <td>&nbsp;</td>
        <td class="container">
          <div class="content">
            <!-- START CENTERED WHITE CONTAINER -->
            <table role="presentation" class="main">
              <!-- START MAIN CONTENT AREA -->
              <tr>
                <td class="wrapper">
                  <table
                    role="presentation"
                    border="0"
                    cellpadding="0"
                    cellspacing="0"
                  >
                    <tr>
                      <td>
                        <p>Hi {{ .FirstName}},</p>
                        <p>
                          Forgot password? Send a PATCH request to with your
                          password and passwordConfirm to {{.URL}}
                        </p>
                        <table
                          role="presentation"
                          border="0"
                          cellpadding="0"
                          cellspacing="0"
                          class="btn btn-primary"
                        >
                          <tbody>
                            <tr>
                              <td align="left">
                                <table
                                  role="presentation"
                                  border="0"
                                  cellpadding="0"
                                  cellspacing="0"
                                >
                                  <tbody>
                                    <tr>
                                      <td>
                                        <a href="{{.URL}}" target="_blank"
                                          >Reset password</a
                                        >
                                      </td>
                                    </tr>
                                  </tbody>
                                </table>
                              </td>
                            </tr>
                          </tbody>
                        </table>
                        <p>
                          If you didn't forget your password, please ignore this
                          email
                        </p>
                        <p>Good luck! Codevo CEO.</p>
                      </td>
                    </tr>
                  </table>
                </td>
              </tr>

              <!-- END MAIN CONTENT AREA -->
            </table>
            <!-- END CENTERED WHITE CONTAINER -->
          </div>
        </td>
        <td>&nbsp;</td>
      </tr>
    </table>
  </body>
</html>

Step 4 – Encoding/Decoding the Password Reset Code

Now, pay attention to this part: we will create two functions to encode and decode the 20-byte string that will be generated by the randstr package.

utils/encode.go


package utils

import "encoding/base64"

func Encode(s string) string {
	data := base64.StdEncoding.EncodeToString([]byte(s))
	return string(data)
}

func Decode(s string) (string, error) {
	data, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return "", err
	}

	return string(data), nil
}


Step 5 – Create a Utility Function to Send the Emails

Golang comes with a standard SMTP package that implements the Simple Mail Transfer Protocol as defined in RFC 5321 but we will use the Gomail package to easily send the emails.


go get gopkg.in/gomail.v2 github.com/k3a/html2text

  • Gomail is a simple and efficient package for sending SMTP emails in Goland
  • HTMLtoText is a simple package used for converting HTML to plain text in Golang.

Now let’s evoke the template.ParseFiles function provided by the html/template package to parse all the nested templates into a cache. The function will also ensure that the templates defined with {{define}} are independent of each other.

utils/email.go


package utils

import (
	"bytes"
	"crypto/tls"
	"html/template"
	"log"
	"os"
	"path/filepath"

	"github.com/k3a/html2text"
	"github.com/wpcodevo/golang-gorm-postgres/initializers"
	"github.com/wpcodevo/golang-gorm-postgres/models"
	"gopkg.in/gomail.v2"
)

type EmailData struct {
	URL       string
	FirstName string
	Subject   string
}

// ? Email template parser

func ParseTemplateDir(dir string) (*template.Template, error) {
	var paths []string
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			paths = append(paths, path)
		}
		return nil
	})

	if err != nil {
		return nil, err
	}

	return template.ParseFiles(paths...)
}

func SendEmail(user *models.User, data *EmailData, emailTemp string) {
	config, err := initializers.LoadConfig(".")

	if err != nil {
		log.Fatal("could not load config", err)
	}

	// Sender data.
	from := config.EmailFrom
	smtpPass := config.SMTPPass
	smtpUser := config.SMTPUser
	to := user.Email
	smtpHost := config.SMTPHost
	smtpPort := config.SMTPPort

	var body bytes.Buffer

	template, err := ParseTemplateDir("templates")
	if err != nil {
		log.Fatal("Could not parse template", err)
	}

	template.ExecuteTemplate(&body, emailTemp, &data)

	m := gomail.NewMessage()

	m.SetHeader("From", from)
	m.SetHeader("To", to)
	m.SetHeader("Subject", data.Subject)
	m.SetBody("text/html", body.String())
	m.AddAlternative("text/plain", html2text.HTML2Text(body.String()))

	d := gomail.NewDialer(smtpHost, smtpPort, smtpUser, smtpPass)
	d.TLSConfig = &tls.Config{InsecureSkipVerify: true}

	// Send Email
	if err := d.DialAndSend(m); err != nil {
		log.Fatal("Could not send email: ", err)
	}

}


In the above, we created a SendEmail function that we will evoke in the route handlers to send any kind of email.

Step 6 – Add the Forgot Password Route Handler

In this section, you will create the forgot password route handler that will be called to send the SMTP email to the user.

Before that, install the randstr package to help us generate the string that will be used as the password reset token.

In this example, we will use a 20-byte string but feel free to change the number of bytes.


go get -u github.com/thanhpk/randstr

controllers/auth.controller.go


func (ac *AuthController) ForgotPassword(ctx *gin.Context) {
	var payload *models.ForgotPasswordInput

	if err := ctx.ShouldBindJSON(&payload); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})
		return
	}

	message := "You will receive a reset email if user with that email exist"

	var user models.User
	result := ac.DB.First(&user, "email = ?", strings.ToLower(payload.Email))
	if result.Error != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "Invalid email or Password"})
		return
	}

	if !user.Verified {
		ctx.JSON(http.StatusUnauthorized, gin.H{"status": "error", "message": "Account not verified"})
		return
	}

	config, err := initializers.LoadConfig(".")
	if err != nil {
		log.Fatal("Could not load config", err)
	}

	// Generate Verification Code
	resetToken := randstr.String(20)

	passwordResetToken := utils.Encode(resetToken)
	user.PasswordResetToken = passwordResetToken
	user.PasswordResetAt = time.Now().Add(time.Minute * 15)
	ac.DB.Save(&user)

	var firstName = user.Name

	if strings.Contains(firstName, " ") {
		firstName = strings.Split(firstName, " ")[1]
	}

	// ? Send Email
	emailData := utils.EmailData{
		URL:       config.ClientOrigin + "/resetpassword/" + resetToken,
		FirstName: firstName,
		Subject:   "Your password reset token (valid for 10min)",
	}

	utils.SendEmail(&user, &emailData, "resetPassword.html")

	ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": message})
}

Let’s evaluate the above code. First, we queried the database to check if a user with that email exists.

Then, we generated the password reset token and called the utils.Encode() function to hash the token.

Next, we stored the hashed token in the database, generated the HTML template, and sent the plain token to the user’s email.

The password reset link contains the reset token that will be used to verify the user’s identity before allowing them to reset their password.

Step 7 – Add the Reset Password Route Handler

In this section, you will create the password reset route handler that will be called to reset the user’s password.

Here, we will extract the unhashed password reset token from the request URL, hash it and query the database to check if the user belonging to the token exists.

Then, we will hash the new password with the Golang bcrypt package and update the user’s password data in the database.

controllers/auth.controller.go


func (ac *AuthController) ResetPassword(ctx *gin.Context) {
	var payload *models.ResetPasswordInput
	resetToken := ctx.Params.ByName("resetToken")

	if err := ctx.ShouldBindJSON(&payload); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})
		return
	}

	if payload.Password != payload.PasswordConfirm {
		ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "Passwords do not match"})
		return
	}

	hashedPassword, _ := utils.HashPassword(payload.Password)

	passwordResetToken := utils.Encode(resetToken)

	var updatedUser models.User
	result := ac.DB.First(&updatedUser, "password_reset_token = ? AND password_reset_at > ?", passwordResetToken, time.Now())
	if result.Error != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "The reset token is invalid or has expired"})
		return
	}

	updatedUser.Password = hashedPassword
	updatedUser.PasswordResetToken = ""
	ac.DB.Save(&updatedUser)

	ctx.SetCookie("token", "", -1, "/", "localhost", false, true)

	ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Password data updated successfully"})
}

Step 8 – Create/Update the Routes File

Now that we have the route handlers defined, add them to the routes file to enable the Gin Gonic framework to register them.

routes/auth.routes.go


package routes

import (
	"github.com/gin-gonic/gin"
	"github.com/wpcodevo/golang-gorm-postgres/controllers"
	"github.com/wpcodevo/golang-gorm-postgres/middleware"
)

type AuthRouteController struct {
	authController controllers.AuthController
}

func NewAuthRouteController(authController controllers.AuthController) AuthRouteController {
	return AuthRouteController{authController}
}

func (rc *AuthRouteController) AuthRoute(rg *gin.RouterGroup) {
	router := rg.Group("/auth")

	router.POST("/register", rc.authController.SignUpUser)
	router.POST("/login", rc.authController.SignInUser)
	router.GET("/logout", middleware.DeserializeUser(), rc.authController.LogoutUser)
	router.GET("/verifyemail/:verificationCode", rc.authController.VerifyEmail)
	router.POST("/forgotpassword", rc.authController.ForgotPassword)
	router.PATCH("/resetpassword/:resetToken", rc.authController.ResetPassword)
}


Conclusion

Through this article, you’ve learned how to add a secure password reset feature to a Golang application. Hackers are always exploiting new ways to hack systems and it’s your duty as a developer to put security measures in place to mitigate data breaches that may put the organization’s reputation in a jeopardy.

Check out the source code: