Organizations are all the time on the lookout for revolutionary methods to deal with threats and vulnerabilities on their platform. They’re continually investing in human assets and applied sciences to assist construct and ship safe functions. Two-factor authentication, Authenticator app, and Biometrics, amongst others, are among the revolutionary strategies organizations are adopting to maintain their platform protected.
On this publish, we are going to discover ways to construct APIs that authenticate customers with their cellphone numbers utilizing Go and Twilio’s Verification Service. For this publish, we might be utilizing Gin-gonic to construct our API. Nevertheless, the identical strategy additionally applies to any Go-based framework.
Conditions
To completely grasp the ideas offered on this tutorial, the next necessities apply:
- Fundamental understanding of Go
- Go set up ( Model 1.18 above)
- A Twilio account; signup for a trial account is totally free.
Getting began
To get began, we have to navigate to the specified listing and run the command under in our terminal:
mkdir go-sms-verification && cd go-sms-verification
This command creates a go-sms-verification
folder and navigates into the challenge listing.
Subsequent, we have to initialize a Go module to handle challenge dependencies by operating the command under:
go mod init go-sms-verification
This command will create a go.mod
file for monitoring challenge dependencies.
We proceed to put in the required dependencies with:
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 constructing internet utility.
github.com/twilio/twilio-go
is a Go package deal for speaking with Twilio.
github.com/joho/godotenv
is a library for managing atmosphere variable.
github.com/go-playground/validator/v10
is a library for validating structs and fields.
Structuring our utility
It’s important to have challenge construction because it makes the challenge maintainable and makes it simpler for us and others to learn our codebase.
To do that, we have to create an api
, cmd
, and knowledge
folder in our challenge listing.
api
is for structuring our API-related recordsdata
cmd
is for structuring our utility entry level
knowledge
is for structuring our utility knowledge
Establishing Twilio
To allow OTP verification in our API, we have to signal into our Twilio Console to get our Account SID and an Auth Token. We have to hold these parameters useful as we’d like them to configure and construct our APIs.
Create a Verification Service
Twilio ships with safe and strong providers for seamlessly validating customers with SMS, Voice, and E-mail. In our case, we are going to use the SMS choice to confirm customers by means of cellphone numbers. To do that, navigate to the Discover Merchandise tab, scroll to the Account safety part, and click on on the Confirm button.
Navigate to the Companies tab, click on on the Create new button, enter sms-service because the pleasant title, toggle-on the SMS choice, and Create.
Upon creation, we have to copy the Service SID. It should additionally turn out to be useful when constructing our API.
Allow geographical permission
Geographical Permissions are mechanisms put in place by Twilio to regulate using their providers. It supplies a device for enabling and disabling nations receiving voice calls and SMS messages from a Twilio account.
To allow SMS, we have to seek for SMS Geographic Permissions within the search bar, click on on the SMS Geographic Permissions consequence, after which test the nation the place the SMS supplier operates.
Creating OTP Verification APIs in Go
With the configuration achieved, we will begin constructing our APIs by following the steps.
Add atmosphere variables
Subsequent, we have to create a .env
file within the root listing and add the snippet under:
TWILIO_ACCOUNT_SID=<ACCOUNT SID>
TWILIO_AUTHTOKEN=<AUTH TOKEN>
TWILIO_SERVICES_ID=<SERVICE ID>
As talked about earlier, we will get the required credentials from Twilio console and Service Setting.
Lastly, we have to create helper capabilities for loading the atmosphere variables into our utility. To do that, we have to create a config.go
file contained in the api
folder and add the snippet under:
package deal 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.Deadly("Error loading .env file")
}
return os.Getenv("TWILIO_ACCOUNT_SID")
}
func envAUTHTOKEN() string {
err := godotenv.Load()
if err != nil {
log.Deadly("Error loading .env file")
}
return os.Getenv("TWILIO_AUTHTOKEN")
}
func envSERVICESID() string {
err := godotenv.Load()
if err != nil {
log.Deadly("Error loading .env file")
}
return os.Getenv("TWILIO_SERVICES_ID")
}
The snippet above does the next:
- Imports the required dependencies
- Create an
envACCOUNTSID
,envAUTHTOKEN
, andenvSERVICESID
capabilities that test if the atmosphere variable is accurately loaded and returns the atmosphere variable.
Create the API fashions
Subsequent, we have to create fashions to characterize our utility knowledge. To do that, we have to navigate to the knowledge
folder and, on this folder, create a mannequin.go
file and add the snippet under:
package deal knowledge
sort OTPData struct {
PhoneNumber string `json:"phoneNumber,omitempty" validate:"required"`
}
sort VerifyData struct {
Consumer *OTPData `json:"consumer,omitempty" validate:"required"`
Code string `json:"code,omitempty" validate:"required"`
}
Create the API routes, helpers, service, and handlers
With the fashions to ship and confirm OTP totally arrange, we have to navigate to the api
folder and do the next:
First, we have to create a route.go
file for configuring the API routes and add the snippet under:
package deal api
import "github.com/gin-gonic/gin"
sort Config struct {
Router *gin.Engine
}
func (app *Config) Routes() {
//routes will come right here
}
The snippet above does the next:
- Imports the required dependency
- Creates a
Config
struct with aRouter
property to configure the applying strategies - Creates a
Routes
perform that takes within theConfig
struct as a pointer
Secondly, we have to create a helper.go
file and add the snippet under:
package deal api
import (
"web/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
sort jsonResponse struct {
Standing int `json:"standing"`
Message string `json:"message"`
Information any `json:"knowledge"`
}
var validate = validator.New()
func (app *Config) validateBody(c *gin.Context, knowledge any) error {
//validate the request physique
if err := c.BindJSON(&knowledge); err != nil {
return err
}
//use the validator library to validate required fields
if err := validate.Struct(&knowledge); err != nil {
return err
}
return nil
}
func (app *Config) writeJSON(c *gin.Context, standing int, knowledge any) {
c.JSON(standing, jsonResponse{Standing: standing, Message: "success", Information: knowledge})
}
func (app *Config) errorJSON(c *gin.Context, err error, standing ...int) {
statusCode := http.StatusBadRequest
if len(standing) > 0 {
statusCode = standing[0]
}
c.JSON(statusCode, jsonResponse{Standing: statusCode, Message: err.Error()})
}
The snippet above does the next:
- Imports the required dependencies
- Creates a
jsonResponse
struct andvalidate
variable to explain the API response and to validate the API fields - Creates a
validateBody
perform that takes within theConfig
struct as a pointer and returns anerror
. Contained in the perform, we validate that requestknowledge
within the appropriate format and in addition use the validator library to additionally validate and test for the required fields - Creates a
writeJSON
perform that takes within theConfig
struct as a pointer and makes use of thejsonResponse
struct to assemble API response when there’s no error - Creates a
errorJSON
perform that takes within theConfig
struct as a pointer and makes use of thejsonResponse
struct to assemble API response when there’s an error
Thirdly, we have to create a service.go
file for abstracting the applying logic and add the snippet under:
package deal api
import (
"github.com/twilio/twilio-go"
twilioApi "github.com/twilio/twilio-go/relaxation/confirm/v2"
)
var consumer *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 := consumer.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 := consumer.VerifyV2.CreateVerificationCheck(envSERVICESID(), params)
if err != nil {
return err
} else if *resp.Standing == "authorised" {
return nil
}
return nil
}
The snippet above does the next:
- Imports the required dependencies
- Creates a
consumer
variable to configure Twilio consumer utilizing the Account SID and Auth Token - Creates a
twilioSendOTP
perform that accepts aphoneNumber
, takes within theConfig
struct as a pointer, and returns both astring
or anerror
. Contained in the perform, we created aparams
variable by including thephoneNumber
and setting the channel for sending the OTP assms
. Lastly, we use theconsumer
variable to create verification by utilizing the Service SID andparams
after which return the suitable response - Creates a
twilioVerifyOTP
perform that accepts aphoneNumber
andcode
, takes within theConfig
struct as a pointer, and returns anerror
. Contained in the perform, we created aparams
variable by including thephoneNumber
andcode
. Lastly, we use theconsumer
variable to test authenticity of the OTP by utilizing the Service SID andparams
after which return the suitable response
Fourthly, we have to create a handler.go
file for modifying the incoming request and add the snippet under:
package deal api
import (
"context"
"go-sms-verification/knowledge"
"web/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 knowledge.OTPData
defer cancel()
app.validateBody(c, &payload)
newData := knowledge.OTPData{
PhoneNumber: payload.PhoneNumber,
}
_, err := app.twilioSendOTP(newData.PhoneNumber)
if err != nil {
app.errorJSON(c, err)
return
}
app.writeJSON(c, http.StatusAccepted, "OTP despatched efficiently")
}
}
func (app *Config) verifySMS() gin.HandlerFunc {
return func(c *gin.Context) {
_, cancel := context.WithTimeout(context.Background(), appTimeout)
var payload knowledge.VerifyData
defer cancel()
app.validateBody(c, &payload)
newData := knowledge.VerifyData{
Consumer: payload.Consumer,
Code: payload.Code,
}
err := app.twilioVerifyOTP(newData.Consumer.PhoneNumber, newData.Code)
if err != nil {
app.errorJSON(c, err)
return
}
app.writeJSON(c, http.StatusAccepted, "OTP verified efficiently")
}
}
The snippet above does the next:
- Imports the required dependencies
- Creates an
appTimeout
variable to set request timeout - Creates a
sendSMS
perform that returns a Gin-gonic handler and takes within theConfig
struct as a pointer. Contained in the returned handler, we outlined the API timeout, used the helper capabilities and the service created earlier to confirm the request physique and ship the OTP - Creates a
verifySMS
perform that returns a Gin-gonic handler and takes within theConfig
struct as a pointer. Contained in the returned handler, we outlined the API timeout, used the helper capabilities and the service created earlier to confirm the request physique and the OTP
Lastly, we have to replace the routes.go
recordsdata the API route and corresponding handler
package deal api
import "github.com/gin-gonic/gin"
sort Config struct {
Router *gin.Engine
}
//modify under
func (app *Config) Routes() {
app.Router.POST("/otp", app.sendSMS())
app.Router.POST("/verifyOTP", app.verifySMS())
}
Placing all of it collectively
With our API totally arrange, we have to create the applying entry level. To do that, we have to navigate to the cmd
folder and, on this folder, create a principal.go
file and add the snippet under:
package deal principal
import (
"go-sms-verification/api"
"github.com/gin-gonic/gin"
)
func principal() {
router := gin.Default()
//initialize config
app := api.Config{Router: router}
//routes
app.Routes()
router.Run(":80")
}
The snippet above does the next:
- Imports the required dependencies
- Creates a Gin router utilizing the
Default
configuration - Initialize the
Config
struct by passing within theRouter
- Provides the route and run the applying on port
:80
With that achieved, we will begin a improvement server utilizing the command under:
go run cmd/principal.go
We will additionally confirm the message logs by navigating to the Confirm tab of the Logs on Twilio
Conclusion
This publish mentioned methods to create APIs that test and confirm customers with their cellphone numbers utilizing Go and Twilio’s Verification Service. Past SMS-based verification, Twilio ships a number of providers to seamlessly combine authentication into a brand new or current codebase.
These assets could be useful: