Introduction 👋

Hey there, fellow developers! Today, we're diving into authentication in Go applications using GToken, a powerful middleware for the GoFrame framework. If you're building web applications in Go and need a robust auth system, you're in the right place!

What We'll Cover 📝

  • Setting up GToken in your GoFrame app
  • Building a secure authentication flow
  • Handling tokens like a pro
  • Real-world examples you can use today
  • Best practices and common pitfalls

Why GToken? 🤔

Before we dive in, you might be wondering: "Why should I use GToken instead of writing my own auth system or using other solutions?"

Here's why:

  • 🚀 Quick to implement
  • 🔒 Built-in security features
  • 🛠️ Highly configurable
  • 🎯 Specifically designed for GoFrame
  • 💪 Production-ready

Getting Started 🚀

First things first, let's get our dependencies set up:

go get github.com/goflyfox/gtoken
go get github.com/gogf/gf/v2

The Basic Setup 🏗️

Let's start with a simple authentication setup. I'll show you the minimal configuration needed to get up and running:

package main

import (
    "github.com/goflyfox/gtoken/gtoken"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/ghttp"
)

func main() {
    // Create a basic GToken instance
    gfToken := &gtoken.GfToken{
        ServerName: "my-awesome-app",
        LoginPath: "/login",
        // We'll add our login handler soon!
        LoginBeforeFunc: LoginHandler,
        // No authentication needed for public routes
        AuthExcludePaths: g.SliceStr{"/public"},
    }

    // That's it! Let's start our server
    s := g.Server()
    s.Group("/", func(group *ghttp.RouterGroup) {
        gfToken.Middleware(context.Background(), group)
        // Your protected routes go here
        group.ALL("/profile", getProfile)
    })
    s.Run()
}

Pretty clean, right? 😎

Building the Login Flow 🔑

Here's where things get interesting. Let's create a login handler that actually does something useful:

func LoginHandler(r *ghttp.Request) (string, interface{}) {
    username := r.Get("username").String()
    password := r.Get("password").String()

    // 🚨 Pro tip: Never store passwords in plain text!
    // This is just an example
    if isValidUser(username, password) {
        // Return user ID and any custom data you want to store
        return username, map[string]interface{}{
            "role": "user",
            "name": "John Doe",
            // Add any user data you need in your app
        }
    }

    r.Response.WriteJson(gtoken.Fail("Invalid credentials"))
    r.ExitAll()
    return "", nil
}

func isValidUser(username, password string) bool {
    // In a real app, you'd check against your database
    // and use proper password hashing
    return username == "demo" && password == "demo123"
}

Making It Production-Ready 🚀

Now, let's add some real-world features that you'll definitely need in production:

1. Token Configuration

gfToken := &gtoken.GfToken{
    // Previous config...
    Timeout: 24 * 60 * 60 * 1000, // 24 hours
    MaxRefresh: 72 * 60 * 60 * 1000, // 3 days
    MultiLogin: true,  // Allow users to login from multiple devices
    EncryptKey: []byte(os.Getenv("TOKEN_SECRET")), // Always use env vars for secrets!
}

2. Redis Storage for Scalability

# config.yaml
redis:
  default:
    address: ${REDIS_HOST}:${REDIS_PORT}
    db: 1
    pass: ${REDIS_PASSWORD}
gfToken.CacheMode = 2  // Use Redis mode
gfToken.CacheKey = "myapp:tokens:"

Cool Features You Should Know About 🌟

Multi-device Login Control

// In your login handler
if deviceCount := getUserActiveDevices(username); deviceCount >= 3 {
    r.Response.WriteJson(gtoken.Fail("Too many active devices"))
    r.ExitAll()
    return
}

Automatic Token Refresh

GToken handles token refresh automatically, but you can customize it:

gfToken.MaxRefresh = 30 * 24 * 60 * 60 * 1000  // 30 days

Error Handling Like a Pro 💪

Here's how to handle auth errors gracefully:

func ErrorHandler(r *ghttp.Request) {
    defer func() {
        if err := recover(); err != nil {
            // Log the error (use your favorite logger)
            logger.Error("Authentication error", err)

            // Return a nice error message
            r.Response.WriteJson(gtoken.Json(401, "Oops! Please login again", nil))
            r.ExitAll()
        }
    }()
    r.Middleware.Next()
}

Testing Your Auth System 🧪

Here's a quick test you can run:

func TestLogin(t *testing.T) {
    client := ghttp.NewClient()
    resp, err := client.Post("http://localhost:8080/login", 
        g.Map{
            "username": "demo",
            "password": "demo123",
        },
    )

    assert.Nil(t, err)
    assert.Equal(t, 200, resp.StatusCode)

    // Parse response and check token
    var result g.Map
    resp.Json(&result)
    assert.NotEmpty(t, result["token"])
}

Common Pitfalls to Avoid ⚠️

  1. Don't store sensitive data in tokens

    • Keep tokens lightweight
    • Store only references to data
  2. Always use HTTPS in production

s.SetHTTPSConfig(&ghttp.ServerHTTPSConfig{
       Certificate: "server.crt",
       Key:         "server.key",
   })
  1. Set appropriate timeouts
    • Token lifetime should match your security needs
    • Consider your users' usage patterns

Advanced Use Cases 🔥

Custom Token Format

Want to customize your token format? Here's how:

type CustomTokenData struct {
    UserID    string    `json:"userId"`
    Role      string    `json:"role"`
    Tenant    string    `json:"tenant"`
    LastLogin time.Time `json:"lastLogin"`
}

func LoginHandler(r *ghttp.Request) (string, interface{}) {
    // ... authentication logic ...

    tokenData := CustomTokenData{
        UserID:    "user123",
        Role:      "admin",
        Tenant:    "org_xyz",
        LastLogin: time.Now(),
    }

    return tokenData.UserID, tokenData
}

Rate Limiting

Here's a simple rate limiter middleware you can add:

func RateLimiter(r *ghttp.Request) {
    key := "rate:" + r.GetClientIp()
    count, err := g.Redis().Get(r.Context(), key)

    if err != nil {
        panic("Rate limiter error")
    }

    if count.Int() > 100 { // 100 requests per minute
        r.Response.WriteStatusExit(429, "Too many requests")
        return
    }

    g.Redis().Incr(r.Context(), key)
    g.Redis().Expire(r.Context(), key, 60) // Reset after 1 minute

    r.Middleware.Next()
}

Debugging Tips 🐛

1. Token Inspection

Here's a handy endpoint to debug token contents:

func inspectToken(r *ghttp.Request) {
    tokenData := gfToken.GetTokenData(r)

    debug := map[string]interface{}{
        "token":       r.Header.Get("Authorization"),
        "tokenData":   tokenData,
        "expiresIn":   gfToken.GetTokenExpire(r),
        "permissions": getPermissions(tokenData),
    }

    r.Response.WriteJson(gtoken.Succ("Token inspection", debug))
}

2. Logging Middleware

Add comprehensive logging to track authentication issues:

func LoggingMiddleware(r *ghttp.Request) {
    start := time.Now()

    // Get the token before any potential errors
    token := r.Header.Get("Authorization")

    // Create a copy of the response writer to capture the status code
    writer := responsewriter.New(r.Response.Writer)
    r.Response.Writer = writer

    defer func() {
        duration := time.Since(start)

        log := map[string]interface{}{
            "path":     r.URL.Path,
            "method":   r.Method,
            "duration": duration.String(),
            "status":   writer.Status(),
            "hasToken": token != "",
            "ip":      r.GetClientIp(),
            "userAgent": r.UserAgent(),
        }

        if err := recover(); err != nil {
            log["error"] = err
            // Re-panic after logging
            panic(err)
        }

        // Use your favorite logger here
        logger.Info("Request log", log)
    }()

    r.Middleware.Next()
}

Real-World Scenarios 🌍

Handling Microservices

When using GToken in a microservices architecture:

// Service-to-service authentication
func ValidateServiceToken(r *ghttp.Request) {
    token := r.Header.Get("X-Service-Token")

    if !isValidServiceToken(token) {
        r.Response.WriteStatusExit(401, "Invalid service token")
        return
    }

    r.Middleware.Next()
}

func isValidServiceToken(token string) bool {
    // Verify against your service registry
    // You might want to use mutual TLS instead for service-to-service auth
    return token == os.Getenv("INTERNAL_SERVICE_TOKEN")
}

User Session Management

Track and manage user sessions:

type Session struct {
    UserID    string    `json:"userId"`
    DeviceID  string    `json:"deviceId"`
    LastSeen  time.Time `json:"lastSeen"`
    IPAddress string    `json:"ipAddress"`
    UserAgent string    `json:"userAgent"`
}

func trackSession(r *ghttp.Request) {
    tokenData := gfToken.GetTokenData(r)
    deviceId := r.Header.Get("X-Device-ID")

    session := Session{
        UserID:    tokenData.Id,
        DeviceID:  deviceId,
        LastSeen:  time.Now(),
        IPAddress: r.GetClientIp(),
        UserAgent: r.UserAgent(),
    }

    // Store in Redis with expiration
    key := fmt.Sprintf("session:%s:%s", tokenData.Id, deviceId)
    g.Redis().SetEX(r.Context(), key, session, 24*time.Hour)

    r.Middleware.Next()
}

// List active sessions
func getUserSessions(r *ghttp.Request) {
    tokenData := gfToken.GetTokenData(r)
    pattern := fmt.Sprintf("session:%s:*", tokenData.Id)

    keys, _ := g.Redis().Keys(r.Context(), pattern)
    sessions := make([]Session, 0)

    for _, key := range keys {
        var session Session
        data, _ := g.Redis().Get(r.Context(), key.String())
        data.Struct(&session)
        sessions = append(sessions, session)
    }

    r.Response.WriteJson(gtoken.Succ("Active sessions", sessions))
}

Performance Optimization Tips 💨

  1. Token Size Optimization
// Bad: Including too much data in token
func badTokenData() interface{} {
    return map[string]interface{}{
        "user": map[string]interface{}{
            "id": 123,
            "name": "John",
            "email": "john@example.com",
            "preferences": map[string]interface{}{
                // Lots of unnecessary data
            },
        },
    }
}

// Good: Keep tokens lightweight
func goodTokenData() interface{} {
    return map[string]interface{}{
        "uid": "u123",
        "role": "user",
    }
}
  1. Caching Strategies
func getUserPermissions(r *ghttp.Request) []string {
    tokenData := gfToken.GetTokenData(r)
    cacheKey := fmt.Sprintf("perm:%s", tokenData.Id)

    // Try cache first
    if perms, err := g.Redis().Get(r.Context(), cacheKey); err == nil {
        return perms.Strings()
    }

    // Cache miss - fetch from DB
    permissions := fetchPermissionsFromDB(tokenData.Id)

    // Cache for 15 minutes
    g.Redis().SetEX(r.Context(), cacheKey, permissions, 15*time.Minute)

    return permissions
}

Security Checklist ✅

Before deploying to production, ensure you have:

  • [ ] Implemented HTTPS with proper certificates
  • [ ] Set secure cookie flags
  • [ ] Configured appropriate token timeouts
  • [ ] Implemented rate limiting
  • [ ] Set up monitoring and alerting
  • [ ] Added comprehensive logging
  • [ ] Performed security testing
  • [ ] Documented security procedures
  • [ ] Set up automated security scanning
  • [ ] Created incident response procedures

Wrapping Up 🎁

That's it! You now have a solid foundation for building secure authentication in your Go applications. The complete code for this tutorial is available on GitHub.

What's Next? 🚀

You could extend this setup by:

  • Adding role-based access control
  • Implementing OAuth2 integration
  • Adding rate limiting
  • Building session management

Let me know in the comments what you'd like to learn about next!


Found this helpful? Follow me for more Go tutorials and consider giving this article a ❤️ like!

This article was crafted based on real-world experience implementing GToken in production applications. Feel free to reach out with questions or share your own experiences in the comments below!