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 := >oken.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 := >oken.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 ⚠️
-
Don't store sensitive data in tokens
- Keep tokens lightweight
- Store only references to data
Always use HTTPS in production
s.SetHTTPSConfig(&ghttp.ServerHTTPSConfig{
Certificate: "server.crt",
Key: "server.key",
})
-
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 💨
- 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",
}
}
- 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!