Организации всегда ищут инновационные способы борьбы с угрозами и уязвимостями на своей платформе. Они постоянно инвестируют в человеческие ресурсы и технологии, помогая создавать и поставлять безопасные приложения. Двухфакторная аутентификация, приложение Authenticator и биометрия, среди прочего, являются одними из инновационных методов, которые организации внедряют для обеспечения безопасности своей платформы.

В этом посте мы узнаем, как создавать API-интерфейсы, которые аутентифицируют пользователей по их номерам телефонов с помощью Go и службы проверки Twilio. В этом посте мы будем использовать Gingonic для создания нашего API. Однако тот же подход применим и к любому фреймворку на основе Go.


Предпосылки

Чтобы полностью понять концепции, представленные в этом руководстве, применяются следующие требования:

  • Базовое понимание Go
  • Перейти к установке (версия 1.18 и выше)
  • учетная запись Twilio; зарегистрироваться для пробного аккаунта совершенно бесплатно.


Начиная

Для начала нам нужно перейти в нужный каталог и выполнить команду ниже в нашем терминале:

mkdir go-sms-verification && cd go-sms-verification
Войти в полноэкранный режим

Выйти из полноэкранного режима

Эта команда создает go-sms-verification папку и переходит в каталог проекта.

Затем нам нужно инициализировать модуль Go для управления зависимостями проекта, выполнив следующую команду:

go mod init go-sms-verification
Войти в полноэкранный режим

Выйти из полноэкранного режима

Эта команда создаст go.mod файл для отслеживания зависимостей проекта.

Мы приступаем к установке необходимых зависимостей с помощью:

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 это фреймворк для создания веб-приложений.

github.com/twilio/twilio-go — это пакет Go для взаимодействия с Twilio.

github.com/joho/godotenv это библиотека для управления переменной среды.

github.com/go-playground/validator/v10 это библиотека для проверки структур и полей.


Структурирование нашего приложения

Очень важно иметь хорошую структуру проекта, поскольку она делает проект удобным для сопровождения и облегчает нам и другим пользователям чтение нашей кодовой базы.
Для этого нам нужно создать api, cmdа также data папка в каталоге нашего проекта.

каталог проекта

api для структурирования наших файлов, связанных с API

cmd для структурирования нашей точки входа в приложение

data для структурирования данных нашего приложения


Настройка Twilio

Чтобы включить проверку OTP в нашем API, нам нужно войти в наш Консоль Twilio чтобы получить наш SID учетной записи и Токен авторизации. Нам нужно держать эти параметры под рукой, поскольку они нужны нам для настройки и создания наших API.

Учетные данные Twilio

Создать службу проверки
Twilio поставляется с безопасными и надежными сервисами для беспрепятственной проверки пользователей с помощью SMS, голосовой связи и электронной почты. В нашем случае мы будем использовать опцию SMS для проверки пользователей по номерам телефонов. Для этого перейдите в Исследуйте продукты вкладку, перейдите к Безопасность аккаунта раздел и нажмите кнопку Проверять кнопка.

Подтвердить учетную запись

Перейдите к Услуги вкладку, нажмите на Создать новый кнопка, ввод смс-сервис как понятное имя, включите смс вариант, и Создавать.

Создать новую услугу
Введите данные

При создании нам нужно скопировать SID службы. Это также пригодится при создании нашего API.

SID службы

включить граммгеографический посвобождение
Географические разрешения — это механизмы, созданные Twilio для контроля использования своих сервисов. Он предоставляет инструмент для включения и отключения стран, принимающих голосовые вызовы и SMS-сообщения из учетной записи Twilio.

Чтобы включить SMS, нам нужно найти SMS Geographic Permissions в строке поиска, нажать на кнопку Географические разрешения SMS результат, а затем Проверьте страна, в которой работает провайдер SMS.

Поиск
Проверить страну работы


Создание API проверки OTP в Go

Выполнив настройку, мы можем начать создавать наши API, следуя инструкциям.

Добавьте переменные среды
Далее нам нужно создать .env файл в корневом каталоге и добавьте фрагмент ниже:

TWILIO_ACCOUNT_SID=<ACCOUNT SID>
TWILIO_AUTHTOKEN=<AUTH TOKEN>
TWILIO_SERVICES_ID=<SERVICE ID>
Войти в полноэкранный режим

Выйти из полноэкранного режима

Как упоминалось ранее, мы можем получить необходимые учетные данные из консоли Twilio и настроек службы.

твилио консоль
сервисные настройки

Наконец, нам нужно создать вспомогательные функции для загрузки переменных среды в наше приложение. Для этого нам нужно создать config.go файл внутри api папку и добавьте фрагмент ниже:

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")
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Фрагмент выше делает следующее:

  • Импортирует необходимые зависимости
  • Создать envACCOUNTSID, envAUTHTOKENа также envSERVICESID функции, которые проверяют, правильно ли загружена переменная среды, и возвращают переменную среды.

Создание моделей API
Далее нам нужно создать модели для представления данных нашего приложения. Для этого нам нужно перейти к data папку и в этой папке создайте model.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"`
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Создание маршрутов API, помощников, службы и обработчиков.
Когда модели для отправки и проверки OTP полностью настроены, нам нужно перейти к api папку и сделайте следующее:

Во-первых, нам нужно создать route.go файл для настройки маршрутов API и добавьте приведенный ниже фрагмент:

package api

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

type Config struct {
    Router *gin.Engine
}

func (app *Config) Routes() {
    //routes will come here
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Фрагмент выше делает следующее:

  • Импортирует необходимую зависимость
  • Создает Config структура с Router свойство для настройки методов приложения
  • Создает Routes функция, которая принимает в Config структура как указатель

Во-вторых, нам нужно создать helper.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()})
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Фрагмент выше делает следующее:

  • Импортирует необходимые зависимости
  • Создает jsonResponse структура и validate переменная для описания ответа API и проверки полей API
  • Создает validateBody функция, которая принимает в Config struct как указатель и возвращает error. Внутри функции мы проверяем этот запрос data в правильном формате, а также использовать библиотеку валидатора для проверки и проверки обязательных полей.
  • Создает writeJSON функция, которая принимает в Config struct в качестве указателя и использует jsonResponse структура для создания ответа API, когда нет ошибки
  • Создает errorJSON функция, которая принимает в Config struct в качестве указателя и использует jsonResponse структура для построения ответа API при возникновении ошибки

В-третьих, нам нужно создать service.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
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Фрагмент выше делает следующее:

  • Импортирует необходимые зависимости
  • Создает client переменная для настройки клиента Twilio с помощью SID учетной записи а также Токен авторизации
  • Создает twilioSendOTP функция, которая принимает phoneNumberпринимает в Config struct как указатель и возвращает либо string или error. Внутри функции мы создали params переменная, добавив phoneNumber и установить канал для отправки OTP как sms. Наконец, мы используем client переменная для создания проверки с помощью SID службы а также params а затем вернуть соответствующий ответ
  • Создает twilioVerifyOTP функция, которая принимает phoneNumber а также codeпринимает в Config struct как указатель и возвращает error. Внутри функции мы создали params переменная, добавив phoneNumber а также code. Наконец, мы используем client переменная для проверки подлинности OTP с помощью SID службы а также params а затем вернуть соответствующий ответ

В-четвертых, нам нужно создать handler.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")
    }
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Фрагмент выше делает следующее:

  • Импортирует необходимые зависимости
  • Создает appTimeout переменная для установки времени ожидания запроса
  • Создает sendSMS функция, которая возвращает обработчик Gingonic и принимает Config структура как указатель. Внутри возвращаемого обработчика мы определили таймаут API, использовали вспомогательные функции и созданный ранее сервис для проверки тела запроса и отправки OTP.
  • Создает verifySMS функция, которая возвращает обработчик Gingonic и принимает Config структура как указатель. Внутри возвращаемого обработчика мы определили таймаут API, использовали вспомогательные функции и созданный ранее сервис для проверки тела запроса и OTP.

Наконец, нам нужно обновить routes.go файлы маршрут API и соответствующий обработчик

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())
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Собираем все вместе
Когда наш API полностью настроен, нам нужно создать точку входа в приложение. Для этого нам нужно перейти к cmd папку и в этой папке создайте main.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")
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Фрагмент выше делает следующее:

  • Импортирует необходимые зависимости
  • Создает маршрутизатор Gin, используя Default конфигурация
  • Инициализировать Config struct, передав в Router
  • Добавляет маршрут и запускает приложение на порту :80

После этого мы можем запустить сервер разработки, используя следующую команду:

go run cmd/main.go 
Войти в полноэкранный режим

Выйти из полноэкранного режима

отправить одноразовый пароль
одноразовый пароль
Проверка одноразового пароля

Мы также можем проверить журналы сообщений, перейдя к Проверять вкладка Журналы Твилио

Журнал сообщений


Вывод

В этом посте обсуждалось, как создавать API-интерфейсы, которые проверяют и верифицируют пользователей по их номерам телефонов с помощью службы проверки Go и Twilio. Помимо проверки на основе SMS, Twilio предоставляет несколько сервисов для беспрепятственной интеграции аутентификации в новую или существующую кодовую базу.

Эти ресурсы могут быть полезны: