Home >Backend Development >Golang >Build an OTP-Based Authentication Server with Go: Part 3
This installment details implementing OTP delivery via Twilio, optimizing OTP sending asynchronously using goroutines, and establishing a robust token-based authentication system.
The core function for sending OTPs using Twilio's messaging API is presented below:
<code class="language-go">func (app *application) sendOTPViaTwilio(otp, phoneNumber string) error { client := twilio.NewRestClientWithParams(twilio.ClientParams{ Username: os.Getenv("TWILIO_SID"), Password: os.Getenv("TWILIO_API_KEY"), }) params := &api.CreateMessageParams{} params.SetBody(fmt.Sprintf( "Thank you for choosing Cheershare! Your one-time password is %v.", otp, )) params.SetFrom(os.Getenv("TWILIO_PHONE_NUMBER")) params.SetTo(fmt.Sprintf("+91%v", phoneNumber)) const maxRetries = 3 var lastErr error for attempt := 1; attempt <= maxRetries; attempt++ { resp, err := client.SendSms(params) if err == nil { app.logger.Printf("Message SID: %s", resp.Sid) return nil } lastErr = err time.Sleep(time.Duration(attempt) * 100 * time.Millisecond) } return fmt.Errorf("failed to send OTP after %d retries: %w", maxRetries, lastErr) }</code>
This function utilizes Twilio's Go SDK to send messages. The from
number is a pre-configured Twilio number. A retry mechanism is included for reliability.
Sequential OTP sending hinders server performance. The solution involves utilizing goroutines to handle OTP delivery concurrently. The application
struct is updated:
<code class="language-go">type application struct { wg sync.WaitGroup config config models data.Models logger *log.Logger cache *redis.Client }</code>
A helper function facilitates background task execution:
<code class="language-go">func (app *application) background(fn func()) { app.wg.Add(1) go func() { defer app.wg.Done() defer func() { if err := recover(); err != nil { app.logger.Printf("Error in background function: %v\n", err) } }() fn() }() }</code>
This uses sync.WaitGroup
to manage goroutines, ensuring completion before shutdown.
A new database table is created to store user tokens:
<code class="language-sql">-- 000002_create-token.up.sql CREATE TABLE IF NOT EXISTS tokens ( hash bytea PRIMARY KEY, user_id bigint NOT NULL REFERENCES users ON DELETE CASCADE, expiry timestamp(0) with time zone NOT NULL, scope text NOT NULL ); -- 000002_create-token.down.sql DROP TABLE IF EXISTS tokens;</code>
This table stores hashed tokens, user IDs, expiry times, and token scopes. Database migration is performed using migrate
.
The data/models.go
file includes functions for token generation, insertion, and retrieval:
<code class="language-go">// ... (other imports) ... package data // ... (other code) ... func generateToken(userId int64, ttl time.Duration, scope string) (*Token, error) { // ... (token generation logic) ... } func (m TokenModel) Insert(token *Token) error { // ... (database insertion logic) ... } func (m TokenModel) DeleteAllForUser(scope string, userID int64) error { // ... (database deletion logic) ... } func (m TokenModel) New(userId int64, ttl time.Duration, scope string) (*Token, error) { // ... (token creation and insertion logic) ... }</code>
This code handles token creation, hashing, and database interaction. The New
function creates and stores new tokens.
The cmd/api/user.go
file's signup handler is modified to issue tokens upon successful OTP verification:
<code class="language-go">// ... (other functions) ... func (app *application) handleUserSignupAndVerification(w http.ResponseWriter, r *http.Request) { // ... (input parsing and validation) ... // ... (OTP generation and sending logic) ... // ... (OTP verification logic) ... // ... (user creation or retrieval) ... token, err := app.generateTokenForUser(user.ID) if err != nil { // ... (error handling) ... } // ... (success response with token) ... }</code>
This integrates token generation into the signup flow.
Three middleware layers enhance security and request handling: recoverPanic
, authenticate
, and requireAuthenticatedUser
. These are implemented and applied to routes as shown in the original text. Context management functions (contextSetUser
and contextGetUser
) are used to store and retrieve user data within the request context.
The server configuration integrates these middlewares, and the example shows how to protect routes using requireAuthenticatedUser
. Future enhancements include file uploading, graceful shutdown, and metrics integration. The complete code is available on GitHub.
The above is the detailed content of Build an OTP-Based Authentication Server with Go: Part 3. For more information, please follow other related articles on the PHP Chinese website!