Home >Backend Development >Golang >Build an OTP-Based Authentication Server with Go: Part 3

Build an OTP-Based Authentication Server with Go: Part 3

DDD
DDDOriginal
2025-01-10 18:03:43807browse

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.

Sending OTPs with Twilio

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.

Asynchronous OTP Sending with Goroutines

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.

Database Token Table

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.

Token Model and Functions

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.

Signup Handler Updates

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.

Middleware Layers

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!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn