Email Verification and Password Reset Flow using golang

vignesh dharuman
7 min readDec 14, 2020

Hi learners, this is my second article in the series of documenting my learning about building a user auth system using JWT in golang. In the first article, we went over the concepts involved in handling the JWT token. In this article, we are going to build on to the same Authentication system and add the functionalities of Email verification and Password resetting. The code repo can be found here. Lets begin.

(Note: for readers who are following the code repo from the previous article, i have made a few reformatting to the code mainly the UserHandler type is renamed as AuthHandler).

As we are using the email id of the user as the primary identity attribute , it is necessary for us to verify that the provided email id is owned by that user. We achieve this verification procedure by sending a secret token to the email and expect the user to provide the same secret back to the server ( as the user can get the phrase only if he has access to the provided email id). I came across two different approaches for doing this, 1. we can send the secret as a url param in a link which hits the server’s endpoint and we verify the secret. 2. We send a secret phrase to the user mail and ask the user to enter the phrase in our application screen. Personally if found the second approach simple and clean and opted it(do let me know in comments if you find any vulnerabilities with this approach). Before we go through the code that performs this flow, lets first have a look at the code that helps us to send an email.

SendGrid-Golang:

Here we are using SendGrid , which is a cloud-based email service provider that helps us to send email. First we need to create an account in sendgrid. Next follow the integration steps provided here and create your sendgrid API key. Copy the key to somewhere, this is the key that will help our program to get authenticated with the sendgrid server. Also we can create the html templates for our mail content. We can also send plain text content in our mail, but for our purpose we will create a decent looking template which will be rendered in the mail we send. Sendgrid has a section called Dynamic templates, which holds all our template code. You can head over here and create your own template(you can find a reference template in the repo’s template directory). We will get a template_id upon creating the template which can be used in our code to refer to the created template.

So now we have the two components needed, the Sendgrid API key and the template_id. Let’s now go through the code to send email,

We have used the sendgrid-go library that provides the utility methods to send mail with Sendgrid using golang. Here initially we declare the type MailType to represent the different types of Mail we are going to send. In our case, we have two types of mail, MailVerification and PasswordReset. The MailData struct holds the data we are going to send to the template that is rendered in the mail. The Mail struct holds the components of a single mail request. The two important functions of our mail service are CreateMail and SendMail. The CreateMail function takes in a mail request and creates the sendgrid-go mail type. Few points to note here is that, we specify the template which must be rendered in the mail using the template_id that we got during the template creation. We also specify the data that should be used during rendering the template. Here we have specified the Username and the secret code. This data will be dynamically rendered in the template using handlebars. Handlebars are something we use in the template to mention the data that will to be rendered dynamically at runtime. We specify the handlebars in the template using the notation {{ handlebar_name}} (you can find the two handlebars {{Username}} and {{Code}} in the sample templates in the repo).

Now we have our mailing service that we can use to send an email. Lets get back to the email verification flow.

The modification in the routes that we have added to support EmailVerification and PasswordReset are below,

The data models we will use and a corresponding sql schema are as follow:

Email Verification:

Note that we have added a new attribute IsVerified which is a bool type representing whether the user mail is verified or not. Also we have created a new model VerificationData to represent the data stored for performing the verification. The ExpiresAt attribute of the VerificationData is a timestamp value denoting the validity period of the verification data.

So now when a user sign’s up, we have to include the logic of sending a secret code to the provided email id. Below code performs that actions,

Above code adds to the existing sign in logic. When we receive a signup request, we store the user details in the request with the IsVerified attribute set to false denoting that the user mail is not verified and send out a verification mail to the signup mail_id. Also we store the corresponding VerificationData which we will use to check against during our verification process later. So now when we receive a signup request, our system will send a verification mail to the provided mail id.

The next will be the process of verification which is exposed using the endpoint /verify/mail. Firstly upon receiving a verification request we validate the request using the middleware function MiddlewareValidateVerificationData.

MiddlewareValidateVerificationData function parses the VerificationData object given in the request body and encodes the data to a VerificationData struct. We then validate the VerificationData struct using the validate tags we provided. If the VerificationData struct has all our required fields, we add the object to the request context and call the next handle function(which in this case may be VerifyMail or VerifyPassReset).

Now lets go over the VerifyMail method which performs the actual verification.

VerifyMail function checks the provided VerificationData to the actual VerificationData in the db that we stored early. We check for the expiration and match of the verification data. If its successful, we change to user verification status to verified by setting the IsVerified attribute to true and remove the verification data from db.

PasswordReset:

Flow for PasswordReset functionality — when the user requests a password reset request, we send a secret to the user’s email. The user will then be prompted by our application screen to enter the secret. If valid secret is entered, we obtain the new password and update it in the db. Lets go through the code that performs this flow,

The first part is to generate a secret code for password reset and mail it to the user.

If you have noticed, the GeneratePassResetCode will be executed after the middlerware function MiddlewareValidateAccessToken(please refer the first article if this is confusing to you), so we will have a valid userID in the request context. We retrieve the user details and send the password reset mail to the user mail and also store the verification data.

The next step is building the endpoint to verify the password reset code,

Most of the things in the above code are same as mail verification flow. One thing to note here is the password reset flow is split into two portions. At first part we just verify the code alone and we don’t take any action. Only at the second step we will do the action of updating the password( as the verification code entering and new password entering will be on two different screens in frontend). We expose these two parts as separate endpoints, so we are not deleting the verification data right after verification like how we do in mail verification as we need to do the verification again at the second part. Also as resetting password is a separate endpoint, we need a way to ensure that the verification(first part) happened before resetting(second part). To achieve this we will send the secret code as a response to the password reset verification and expect to get the secret code in the password resetting request.

The next is the second part , i.e., resetting the password,

Here again the ResetPassword will execute after the middleware function MiddlewareValidateAccessToken, so we will have our userID in the request context. We fetch the user detail and the verification data stored corresponding to the user. We do a basic check on the verification code to check that the verification(first part) has happened. We then hash the new password and update the db with the new password. Also we update the tokenhash of the user during password reset as discussed in the first article. Finally we delete the verification data from the db.

That’s all i have for this article. Hope it helped you. Thanks for your time and happy learning :-)

References:

--

--