Twilio Otp Verification with Golang

Edwin Siby
9 min readJun 3, 2023

--

Twilio OTP (One-Time Password) is a service provided by Twilio, a cloud communications platform, that enables the generation and delivery of one-time passwords to users for authentication purposes. OTPs are temporary codes used to verify the identity of a user during the login or account verification process.

import (
"errors"
"fmt"

"github.com/twilio/twilio-go"
openapi "github.com/twilio/twilio-go/rest/verify/v2"
)

var TWILIO_ACCOUNT_SID string = "AC00fe15eda0000b067d9d21210a46b5"
var TWILIO_AUTH_TOKEN string = "24400e2313800001e49fc1826296083"
var VERIFY_SERVICE_SID string = "VAb0085891600005343f936bb993fa864"
var client *twilio.RestClient = twilio.NewRestClientWithParams(twilio.ClientParams{
Username: TWILIO_ACCOUNT_SID,
Password: TWILIO_AUTH_TOKEN,
})
  1. TWILIO_ACCOUNT_SID: This variable holds your Twilio Account SID (a unique identifier for your Twilio account), which is assigned to your Twilio account when you sign up for their services. It is used to authenticate and identify your account.
  2. TWILIO_AUTH_TOKEN: This variable stores your Twilio Auth Token. The Auth Token is a secret key that works in conjunction with your Account SID to authenticate your requests to the Twilio API. It should be kept secure and not shared publicly.
  3. VERIFY_SERVICE_SID: This variable represents the Service SID for the Verify service in Twilio. A Service SID is a unique identifier for a specific service you've created in your Twilio account. In this case, it is associated with the Verify service, which provides OTP-related functionalities.
  4. client *twilio.RestClient: This line creates a new instance of the twilio.RestClient object, which is used to interact with the Twilio REST API. It allows you to send requests to Twilio's services, such as sending SMS, making voice calls, and managing OTP verification.
  5. twilio.NewRestClientWithParams(twilio.ClientParams{...}): This function initializes the REST client with the provided parameters. It takes a twilio.ClientParams struct as an argument, which includes the authentication credentials (Username and Password), in this case, your Account SID and Auth Token.

Overall, the code snippet sets up the necessary configuration and authentication to interact with Twilio’s services using the Twilio library. It establishes a connection with the Twilio API, allowing you to utilize various features and functionalities, including OTP verification through the Verify service.

func SendOtp(phone string) (string, error) {
to := "+91" + phone
params := &openapi.CreateVerificationParams{}
params.SetTo(to)
params.SetChannel("sms")

resp, err := client.VerifyV2.CreateVerification(VERIFY_SERVICE_SID, params)
if err != nil {
fmt.Println(err.Error())
return "", errors.New("Otp failed to generate")
} else {
fmt.Printf("Sent verification '%s'\n", *resp.Sid)
return *resp.Sid, nil
}
}
  1. func SendOtp(phone string) (string, error): This line declares the function SendOtp, which takes a phone parameter of type string. The function returns a string (the OTP SID) and an error (if any).
  2. to := "+91" + phone: This line prepends the country code "+91" to the phone number to format it appropriately for the destination phone number.
  3. params := &openapi.CreateVerificationParams{}: This line creates a new instance of the openapi.CreateVerificationParams struct, which is used to specify the parameters for creating a verification. It will be passed to the CreateVerification method.
  4. params.SetTo(to): This line sets the destination phone number for the OTP verification by assigning the formatted phone number (to) to the To field of the params struct.
  5. params.SetChannel("sms"): This line sets the channel through which the OTP will be sent. In this case, it specifies that the OTP should be sent via SMS. You can use other channels like "call" or "email" depending on your requirements.
  6. resp, err := client.VerifyV2.CreateVerification(VERIFY_SERVICE_SID, params): This line invokes the CreateVerification method of the VerifyV2 service from the Twilio client. It creates a verification request using the specified service SID (VERIFY_SERVICE_SID) and the provided parameters (params). The response is stored in the resp variable, and any error that occurs during the request is assigned to the err variable.
  7. if err != nil { ... }: This conditional block checks if there was an error during the verification request. If an error occurs, it prints the error message and returns an error indicating that the OTP failed to generate.
  8. fmt.Printf("Sent verification '%s'\n", *resp.Sid): If the verification request is successful, this line prints the verification SID (resp.Sid) to indicate that the OTP verification was sent successfully.
  9. return *resp.Sid, nil: Finally, if there were no errors, the function returns the verification SID (resp.Sid) as a string and a nil error, indicating that the OTP was sent successfully.

In summary, this function uses the Twilio library to send an OTP via SMS to the specified phone number. It utilizes the CreateVerification method, passing the appropriate parameters, and handles any errors that may occur during the process.

func CheckOtp(phone, code string) error {
to := "+91" + phone
params := &openapi.CreateVerificationCheckParams{}
params.SetTo(to)
params.SetCode(code)

resp, err := client.VerifyV2.CreateVerificationCheck(VERIFY_SERVICE_SID, params)

if err != nil {
fmt.Println(err.Error())
return errors.New("Invalid otp")
} else if *resp.Status == "approved" {
return nil
} else {
return errors.New("Invalid otp")
}
}
  1. func CheckOtp(phone, code string) error: This line declares the function CheckOtp, which takes two parameters: phone (the phone number to which the OTP was sent) and code (the OTP entered by the user). The function returns an error if the OTP is invalid.
  2. to := "+91" + phone: This line appends the country code "+91" to the phone number to format it correctly for the destination phone number.
  3. params := &openapi.CreateVerificationCheckParams{}: This line creates a new instance of the openapi.CreateVerificationCheckParams struct, which is used to specify the parameters for OTP verification. It will be passed to the CreateVerificationCheck method.
  4. params.SetTo(to): This line sets the destination phone number for the OTP verification by assigning the formatted phone number (to) to the To field of the params struct.
  5. params.SetCode(code): This line sets the OTP code entered by the user by assigning it to the Code field of the params struct.
  6. resp, err := client.VerifyV2.CreateVerificationCheck(VERIFY_SERVICE_SID, params): This line invokes the CreateVerificationCheck method of the VerifyV2 service from the Twilio client. It performs a verification check using the specified service SID (VERIFY_SERVICE_SID) and the provided parameters (params). The response is stored in the resp variable, and any error that occurs during the request is assigned to the err variable.
  7. if err != nil { ... }: This conditional block checks if there was an error during the verification check. If an error occurs, it prints the error message and returns an error indicating that the OTP is invalid.
  8. else if *resp.Status == "approved" { ... }: If there was no error, this line checks if the verification status (resp.Status) is "approved". If it is, it means the OTP is valid, and the function returns nil, indicating no error.
  9. else { ... }: If the verification status is not "approved", this block is executed. It means the OTP is invalid, so the function returns an error indicating that the OTP is invalid.

In summary, this function uses the Twilio library to check the validity of an OTP entered by the user. It verifies the OTP by calling the CreateVerificationCheck method and passing the appropriate parameters. It handles any errors that may occur during the process and returns an error if the OTP is invalid or nil if it is valid.

USE CASE


func (u *UserUsecase) ExecuteLogin(phone string) (string, error) {
var otpKey entity.OtpKey
result, err := u.userRepo.GetByPhone(phone)
if err != nil {
return "", err
}
if result == nil {
return "", errors.New("user with this phone not found")
}
permission, err := u.userRepo.CheckPermission(result)
if permission == false {
return "", errors.New("user permission denied")
}
key, err1 := utils.SendOtp(phone)
if err1 != nil {
return "", err
} else {
otpKey.Key = key
otpKey.Phone = phone
err = u.userRepo.CreateOtpKey(&otpKey)
if err != nil {
return "", err
}
return key, nil
}

}

The code snippet represents a method called `ExecuteLogin` within the `UserUsecase` struct. Here’s a simplified explanation of what the code does:

1. `var otpKey entity.OtpKey`: Initializes a variable `otpKey` of type `entity.OtpKey`. This variable will be used to store the OTP key and phone number.

2. `result, err := u.userRepo.GetByPhone(phone)`: Calls the `GetByPhone` method of the `userRepo` (User Repository) associated with the `UserUsecase` instance. It retrieves user information based on the provided `phone` number and assigns the result to `result`. If an error occurs, it is stored in `err`.

3. `if err != nil { … }`: Checks if an error occurred during the retrieval of user information. If there is an error, it is returned immediately, indicating a failure in executing the login.

4. `if result == nil { … }`: Checks if the `result` is `nil`, indicating that no user was found with the provided `phone` number. If so, an error is returned, indicating that the user with the provided phone number was not found.

5. `permission, err := u.userRepo.CheckPermission(result)`: Calls the `CheckPermission` method of the `userRepo` to check the user’s permission. It passes the `result` obtained in step 2. The permission result is assigned to `permission`, and any error is stored in `err`.

6. `if permission == false { … }`: Checks if the `permission` is `false`, indicating that the user’s permission is denied. If so, an error is returned, indicating that the user’s permission is denied.

7. `key, err1 := utils.SendOtp(phone)`: Calls the `SendOtp` function from the `utils` package to generate and send an OTP to the provided `phone` number. The generated OTP key is assigned to `key`, and any error is stored in `err1`.

8. `if err1 != nil { … }`: Checks if an error occurred during the OTP generation and sending process. If there is an error, it is returned immediately, indicating a failure in sending the OTP.

9. `else { … }`: If there were no errors, this block is executed. It assigns the generated OTP key and the phone number to the `otpKey` variable.

10. `err = u.userRepo.CreateOtpKey(&otpKey)`: Calls the `CreateOtpKey` method of the `userRepo` to store the OTP key and phone number in the database. It passes a pointer to the `otpKey` variable. Any error that occurs during the database operation is assigned to `err`.

11. `if err != nil { … }`: Checks if an error occurred while storing the OTP key and phone number. If there is an error, it is returned immediately, indicating a failure in storing the OTP key.

12. `return key, nil`: If everything executed successfully, the generated OTP key is returned along with a `nil` error, indicating a successful login initiation.

In summary, the `ExecuteLogin` method retrieves user information based on the provided phone number, checks user permissions, generates and sends an OTP, stores the OTP key and phone number, and returns the OTP key if all operations are successful.

func (uu *UserUsecase) ExecuteOtpValidation(key, otp string) (*entity.User, error) {
result, err := uu.userRepo.GetByKey(key)
if err != nil {
return nil, err
}
user, err := uu.userRepo.GetByPhone(result.Phone)
if err != nil {
return nil, err
}
err1 := utils.CheckOtp(result.Phone, otp)
if err1 != nil {
return nil, err1
}
return user, nil
}

The code snippet represents a method called `ExecuteOtpValidation` within the `UserUsecase` struct. Here’s a simplified explanation of what the code does:

1. `func (uu *UserUsecase) ExecuteOtpValidation(key, otp string) (*entity.User, error)`: Declares a method named `ExecuteOtpValidation` that takes two parameters: `key` (the OTP key) and `otp` (the OTP entered by the user). The method returns a pointer to `entity.User` and an error.

2. `result, err := uu.userRepo.GetByKey(key)`: Calls the `GetByKey` method of the `userRepo` (User Repository) associated with the `UserUsecase` instance. It retrieves user information based on the provided OTP `key` and assigns the result to `result`. If an error occurs, it is stored in `err`.

3. `if err != nil { … }`: Checks if an error occurred during the retrieval of user information. If there is an error, it is returned immediately, indicating a failure in OTP validation.

4. `user, err := uu.userRepo.GetByPhone(result.Phone)`: Calls the `GetByPhone` method of the `userRepo` to retrieve user information based on the phone number associated with the `result`. It assigns the result to `user`, and any error is stored in `err`.

5. `if err != nil { … }`: Checks if an error occurred during the retrieval of user information. If there is an error, it is returned immediately, indicating a failure in OTP validation.

6. `err1 := utils.CheckOtp(result.Phone, otp)`: Calls the `CheckOtp` function from the `utils` package to validate the OTP entered by the user. It passes the phone number associated with `result` and the entered OTP. Any error that occurs during the OTP validation process is assigned to `err1`.

7. `if err1 != nil { … }`: Checks if an error occurred during the OTP validation process. If there is an error, it is returned immediately, indicating an invalid OTP.

8. `return user, nil`: If all operations were successful, the `user` object (retrieved based on the phone number associated with the OTP key) is returned along with a `nil` error, indicating a successful OTP validation.

In summary, the `ExecuteOtpValidation` method retrieves user information based on the provided OTP key, checks if the OTP key is valid, retrieves user information based on the associated phone number, validates the OTP, and returns the user object if the OTP validation is successful.

Have a nice day Gophers.

--

--