How to build a One-Time-Password(OTP) Verification API with Go and Twilio
Tags: go, twilio, gin, otp
Published: 2022-10-29 10:07:43

---
title: How to build a One-Time-Password(OTP) Verification API with Go and Twilio
published: true
description: Learn how to build APIs that authenticate users with their phone numbers using Go and Twilio’s Verification Service
tags: #go #twilio #gin #otp
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/343wdb4xl38pxy2ws9l2.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-10-21 14:24 +0000
---
Organizations are always looking for innovative ways to tackle threats and vulnerabilities on their platform. They are constantly investing in human resources and technologies to help build and ship secure applications. Two-factor authentication, Authenticator app, and Biometrics, among others, are some of the innovative methods organizations are adopting to keep their platform safe.

In this post, we will learn how to build APIs that authenticate users with their phone numbers using Go and Twilio’s Verification Service. For this post, we will be using Gin-gonic to build our API. However, the same approach also applies to any Go-based framework.

## Prerequisites

To fully grasp the concepts presented in this tutorial, the following requirements apply:

- Basic understanding of Go
- Go installation ( Version 1.18 above)
- A Twilio account; [**signup**](https://www.twilio.com/try-twilio) for a trial account is completely free.

## Getting started

To get started, we need to navigate to the desired directory and run the command below in our terminal:

```bash
mkdir go-sms-verification && cd go-sms-verification
```

This command creates a `go-sms-verification` folder and navigates into the project directory.

Next, we need to initialize a Go module to manage project dependencies by running the command below:
```bash
go mod init go-sms-verification
```

This command will create a `go.mod` file for tracking project dependencies.

We proceed to install the required dependencies with:

```bash
go get github.com/gin-gonic/gin github.com/twilio/twilio-go github.com/joho/godotenv github.com/go-playground/validator/v10
```

`github.com/gin-gonic/gin` is a framework for building web application. 

`github.com/twilio/twilio-go` is a Go package for communicating with Twilio.

`github.com/joho/godotenv` is a library for managing environment variable.

`github.com/go-playground/validator/v10` is a library for validating structs and fields.

## Structuring our application

It is essential to have a good project structure as it makes the project maintainable and makes it easier for us and others to read our codebase.
To do this, we need to create an `api`, `cmd`, and `data` folder in our project directory.

![project directory](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665671917075_Screenshot+2022-10-13+at+15.25.26.png)


`api` is for structuring our API-related files

`cmd` is for structuring our application entry point

`data` is for structuring our application data

## Setting up Twilio

To enable OTP verification in our API, we need to sign into our [Twilio Console](https://console.twilio.com/) to get our **Account SID** and an **Auth Token**. We need to keep these parameters handy as we need them to configure and build our APIs.

![Twilio credentials](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665673572014_Screenshot+2022-10-13+at+16.05.12.png)


**Create a Verification Service**
Twilio ships with secure and robust services for seamlessly validating users with SMS, Voice, and Email. In our case, we will use the SMS option to verify users through phone numbers.  To do this, navigate to the **Explore Products** tab, scroll to the **Account security** section, and click on the **Verify** button.

![Verify account](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665673638888_Screenshot+2022-10-08+at+16.44.44.png)


Navigate to the **Services** tab, click on the **Create new** button, input **sms-service** as the friendly name, toggle-on the **SMS** option, and **Create**.

![Create new service](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665674577307_Screenshot+2022-10-08+at+16.46.42.png)
![Input details](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665674577353_Screenshot+2022-10-08+at+16.53.00.png)


Upon creation, we need to copy the **Service SID.** It will also come in handy when building our API.

![Service SID](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665674754011_Screenshot+2022-10-08+at+16.53.39.png)


**Enable** **g****eographical** **p****ermission**
Geographical Permissions are mechanisms put in place by Twilio to control the use of their services. It provides a tool for enabling and disabling countries receiving voice calls and SMS messages from a Twilio account.

To enable SMS, we need to search for SMS Geographic Permissions in the search bar, click on the **SMS Geographic Permissions** result, and then **check** the country where the SMS provider operates.

![Search](https://paper-attachments.dropbox.com/s_3BFCDA37590D5822C6FEFC1F3DD4F8A442FBEC418FAB7D6351861F1A86D14D5A_1663779185609_Screenshot+2022-09-21+at+11.36.38.png)
![Check country of operation](https://paper-attachments.dropbox.com/s_3BFCDA37590D5822C6FEFC1F3DD4F8A442FBEC418FAB7D6351861F1A86D14D5A_1663779216189_Screenshot+2022-09-21+at+17.53.12.png)



## Creating OTP Verification APIs in Go

With the configuration done, we can start building our APIs by following the steps.

**Add environment variables**
Next, we need to create a `.env` file in the root directory and add the snippet below:

```bash
TWILIO_ACCOUNT_SID=<ACCOUNT SID>
TWILIO_AUTHTOKEN=<AUTH TOKEN>
TWILIO_SERVICES_ID=<SERVICE ID>
```

As mentioned earlier, we can get the required credentials from Twilio console and Service Setting.

![twilio console](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665676428573_Screenshot+2022-10-13+at+16.05.12.png)
![service settings](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665676428561_Screenshot+2022-10-08+at+16.53.39.png)


Finally, we need to create helper functions for loading the environment variables into our application. To do this, we need to create a `config.go` file inside the `api` folder and add the snippet below:

```go
package api

import (
    "log"
    "os"

    "github.com/joho/godotenv"
)

func envACCOUNTSID() string {
    println(godotenv.Unmarshal(".env"))
    err := godotenv.Load(".env")
    if err != nil {
        log.Fatalln(err)
        log.Fatal("Error loading .env file")
    }
    return os.Getenv("TWILIO_ACCOUNT_SID")
}

func envAUTHTOKEN() string {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    return os.Getenv("TWILIO_AUTHTOKEN")
}

func envSERVICESID() string {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    return os.Getenv("TWILIO_SERVICES_ID")
}
```

The snippet above does the following:

- Imports the required dependencies
- Create an `envACCOUNTSID`, `envAUTHTOKEN`, and `envSERVICESID` functions that check if the environment variable is correctly loaded and returns the environment variable.

**Create the API models**
Next, we need to create models to represent our application data. To do this, we need to navigate to the `data` folder and, in this folder, create a `model.go` file and add the snippet below:

```go
package data

type OTPData struct {
    PhoneNumber string `json:"phoneNumber,omitempty" validate:"required"`
}

type VerifyData struct {
    User *OTPData  `json:"user,omitempty" validate:"required"`
    Code string `json:"code,omitempty" validate:"required"`
}
```

**Create the API routes, helpers, service, and handlers**
With the models to send and verify OTP fully set up, we need to navigate to the `api` folder and do the following:

First, we need to create a `route.go` file for configuring the API routes and add the snippet below:

```go
package api

import "github.com/gin-gonic/gin"

type Config struct {
    Router *gin.Engine
}

func (app *Config) Routes() {
    //routes will come here
}
```
The snippet above does the following:

- Imports the required dependency
- Creates a `Config` struct with a `Router` property to configure the application methods
- Creates a `Routes` function that takes in the `Config` struct as a pointer

Secondly, we need to create a `helper.go` file and add the snippet below:

```go
package api

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

type jsonResponse struct {
    Status  int    `json:"status"`
    Message string `json:"message"`
    Data    any    `json:"data"`
}

var validate = validator.New()

func (app *Config) validateBody(c *gin.Context, data any) error {
    //validate the request body
    if err := c.BindJSON(&data); err != nil {
        return err
    }
    //use the validator library to validate required fields
    if err := validate.Struct(&data); err != nil {
        return err
    }
    return nil
}

func (app *Config) writeJSON(c *gin.Context, status int, data any) {
    c.JSON(status, jsonResponse{Status: status, Message: "success", Data: data})
}

func (app *Config) errorJSON(c *gin.Context, err error, status ...int) {
    statusCode := http.StatusBadRequest
    if len(status) > 0 {
        statusCode = status[0]
    }
    c.JSON(statusCode, jsonResponse{Status: statusCode, Message: err.Error()})
}
```
The snippet above does the following:

- Imports the required dependencies
- Creates a `jsonResponse` struct and `validate` variable to describe the API response and to validate the API fields
- Creates a `validateBody` function that takes in the `Config` struct as a pointer and returns an `error`. Inside the function, we validate that request `data` in the correct format and also use the validator library to also validate and check for the required fields
- Creates a `writeJSON` function that takes in the `Config` struct as a pointer and uses the `jsonResponse` struct to construct API response when there’s no error
- Creates a `errorJSON` function that takes in the `Config` struct as a pointer and uses the `jsonResponse` struct to construct API response when there’s an error

Thirdly, we need to create a `service.go` file for abstracting the application logic and add the snippet below:

```go

package api

import (
    "github.com/twilio/twilio-go"
    twilioApi "github.com/twilio/twilio-go/rest/verify/v2"
)

var client *twilio.RestClient = twilio.NewRestClientWithParams(twilio.ClientParams{
    Username: envACCOUNTSID(),
    Password: envAUTHTOKEN(),
})

func (app *Config) twilioSendOTP(phoneNumber string) (string, error) {
    params := &twilioApi.CreateVerificationParams{}
    params.SetTo(phoneNumber)
    params.SetChannel("sms")

    resp, err := client.VerifyV2.CreateVerification(envSERVICESID(), params)
    if err != nil {
        return "", err
    }

    return *resp.Sid, nil
}

func (app *Config) twilioVerifyOTP(phoneNumber string, code string) error {
    params := &twilioApi.CreateVerificationCheckParams{}
    params.SetTo(phoneNumber)
    params.SetCode(code)

    resp, err := client.VerifyV2.CreateVerificationCheck(envSERVICESID(), params)
    if err != nil {
        return err
    } else if *resp.Status == "approved" {
        return nil
    }

    return nil
}
```

The snippet above does the following:

- Imports the required dependencies
- Creates a `client` variable to configure Twilio client using the **Account SID** and **Auth Token**
- Creates a `twilioSendOTP` function that accepts a `phoneNumber`, takes in the `Config` struct as a pointer, and returns either a `string` or an `error`. Inside the function, we created a `params` variable by adding the `phoneNumber` and setting the channel for sending the OTP as `sms`. Finally, we use the `client` variable to create verification by using the **Service SID** and `params` and then return the appropriate response
- Creates a `twilioVerifyOTP` function that accepts a `phoneNumber` and `code`, takes in the `Config` struct as a pointer, and returns an `error`. Inside the function, we created a `params` variable by adding the `phoneNumber` and `code`. Finally, we use the `client` variable to check authenticity of the OTP by using the **Service SID** and `params` and then return the appropriate response

Fourthly, we need to create a `handler.go` file for modifying the incoming request and add the snippet below:

```go
package api

import (
    "context"
    "go-sms-verification/data"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
)

const appTimeout = time.Second * 10

func (app *Config) sendSMS() gin.HandlerFunc {
    return func(c *gin.Context) {
        _, cancel := context.WithTimeout(context.Background(), appTimeout)
        var payload data.OTPData
        defer cancel()

        app.validateBody(c, &payload)

        newData := data.OTPData{
            PhoneNumber: payload.PhoneNumber,
        }

        _, err := app.twilioSendOTP(newData.PhoneNumber)
        if err != nil {
            app.errorJSON(c, err)
            return
        }

        app.writeJSON(c, http.StatusAccepted, "OTP sent successfully")
    }
}

func (app *Config) verifySMS() gin.HandlerFunc {
    return func(c *gin.Context) {
        _, cancel := context.WithTimeout(context.Background(), appTimeout)
        var payload data.VerifyData
        defer cancel()

        app.validateBody(c, &payload)

        newData := data.VerifyData{
            User: payload.User,
            Code: payload.Code,
        }

        err := app.twilioVerifyOTP(newData.User.PhoneNumber, newData.Code)
        if err != nil {
            app.errorJSON(c, err)
            return
        }

        app.writeJSON(c, http.StatusAccepted, "OTP verified successfully")
    }
}
```

The snippet above does the following:

- Imports the required dependencies
- Creates an `appTimeout` variable to set request timeout
- Creates a `sendSMS` function that returns a Gin-gonic handler and takes in the `Config` struct as a pointer. Inside the returned handler, we defined the API timeout, used the helper functions and the service created earlier to verify the request body and send the OTP
- Creates a `verifySMS` function that returns a Gin-gonic handler and takes in the `Config` struct as a pointer. Inside the returned handler, we defined the API timeout, used the helper functions and the service created earlier to verify the request body and the OTP

Finally, we need to update the `routes.go` files the API route and corresponding handler

```go
package api

import "github.com/gin-gonic/gin"

type Config struct {
    Router *gin.Engine
}

//modify below
func (app *Config) Routes() {
    app.Router.POST("/otp", app.sendSMS())
    app.Router.POST("/verifyOTP", app.verifySMS())
}
```

**Putting it all together**
With our API fully set up, we need to create the application entry point. To do this, we need to navigate to the `cmd` folder and, in this folder, create a `main.go` file and add the snippet below:

```go
package main

import (
    "go-sms-verification/api"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    //initialize config
    app := api.Config{Router: router}

    //routes
    app.Routes()

    router.Run(":80")
}
```
The snippet above does the following:

- Imports the required dependencies
- Creates a Gin router using the `Default` configuration 
- Initialize the `Config` struct by passing in the `Router`
- Adds the route and run the application on port `:80`

With that done, we can start a development server using the command below:

```go
go run cmd/main.go 
```

![send OTP](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665700959083_Screenshot+2022-10-08+at+18.04.51.png)
![OTP](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665700959117_IMG_8827.PNG)
![Verifying OTP](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665700959120_Screenshot+2022-10-08+at+18.06.00.png)


We can also verify the message logs by navigating to the **Verify** tab of the **Logs** on Twilio

![Message log](https://paper-attachments.dropbox.com/s_5B9EC91BD44DDEBF4CBA993B23ED14FA855B0F9A80137ECFCB22BC7B67C871FC_1665701089112_Screenshot+2022-10-08+at+18.07.02.png)



## Conclusion

This post discussed how to create APIs that check and verify users with their phone numbers using Go and Twilio’s Verification Service. Beyond SMS-based verification, Twilio ships multiple services to seamlessly integrate authentication into a new or existing codebase.

These resources might be helpful:

- [Twilio Verification Service](https://www.twilio.com/docs/verify/api/service)
- [Twilio-go package](https://github.com/twilio/twilio-go)