Gin Basic Authentication
Authentication is a crucial aspect of web applications that ensures only authorized users can access specific resources. In this guide, we'll explore how to implement Basic Authentication in Gin, a popular web framework for Go.
Understanding Basic Authentication
Basic Authentication is one of the simplest HTTP authentication schemes. Despite its name, it provides only a baseline level of security and should be used with HTTPS to encrypt credentials during transmission.
The authentication process follows these steps:
- The client sends a request to a protected resource
- The server responds with a 401 Unauthorized status and a
WWW-Authenticate
header - The client sends credentials (username and password) encoded in Base64 format in the
Authorization
header - The server validates the credentials and grants access if they are valid
Basic Auth Header Format
Authorization: Basic base64(username:password)
Implementing Basic Auth in Gin
Gin provides a built-in middleware that makes it easy to add Basic Authentication to your routes. Let's see how to implement it:
Using Gin's BasicAuth Middleware
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// Create a map of authorized users
authorized := gin.Accounts{
"admin": "password123",
"user": "secret456",
}
// Protected group
protected := r.Group("/admin")
protected.Use(gin.BasicAuth(authorized))
protected.GET("/dashboard", func(c *gin.Context) {
// Get the user from the BasicAuth middleware
user := c.MustGet(gin.AuthUserKey).(string)
c.JSON(http.StatusOK, gin.H{
"message": "Welcome to the admin dashboard",
"user": user,
})
})
// Public routes
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Welcome to the public area",
})
})
r.Run(":8080")
}
What Happens When You Run This Code
When you run this server and try to access the /admin/dashboard
endpoint:
- If you don't provide credentials, you'll see a browser authentication popup
- After entering valid credentials (e.g., username: "admin", password: "password123"), you'll see:
json
{
"message": "Welcome to the admin dashboard",
"user": "admin"
} - Public routes like
/
remain accessible without authentication
Custom Authentication Logic
Sometimes you need more complex authentication rules. You can create a custom Basic Auth middleware:
func CustomBasicAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// Get the Basic Authentication credentials
user, password, hasAuth := c.Request.BasicAuth()
if !hasAuth {
c.Header("WWW-Authenticate", "Basic realm=Restricted")
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// Custom validation logic
if user == "admin" && password == "strongpassword" {
// Store user information for route handlers to use
c.Set(gin.AuthUserKey, user)
c.Next()
return
}
// Handle invalid credentials
c.Header("WWW-Authenticate", "Basic realm=Restricted")
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
}
}
You can use this custom middleware in your routes:
r := gin.Default()
admin := r.Group("/admin")
admin.Use(CustomBasicAuth())
// Protected routes
admin.GET("/reports", func(c *gin.Context) {
// Your code here
})
Real-World Example: API with Database Authentication
Let's create a more practical example that validates users against a database:
package main
import (
"database/sql"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"golang.org/x/crypto/bcrypt"
"log"
"net/http"
)
var db *sql.DB
func main() {
// Initialize database connection
var err error
db, err = sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/mydatabase")
if err != nil {
log.Fatal("Database connection failed:", err)
}
defer db.Close()
r := gin.Default()
// Public routes
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Welcome to our API"})
})
// Protected API routes
api := r.Group("/api")
api.Use(DatabaseBasicAuth())
api.GET("/profile", func(c *gin.Context) {
user := c.MustGet(gin.AuthUserKey).(string)
c.JSON(200, gin.H{"message": "Profile data", "user": user})
})
r.Run(":8080")
}
// DatabaseBasicAuth authenticates against database
func DatabaseBasicAuth() gin.HandlerFunc {
return func(c *gin.Context) {
username, password, hasAuth := c.Request.BasicAuth()
if !hasAuth {
c.Header("WWW-Authenticate", "Basic realm=API Authorization")
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// Query the database for user
var hashedPassword string
err := db.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&hashedPassword)
if err != nil {
c.Header("WWW-Authenticate", "Basic realm=API Authorization")
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// Compare the stored hashed password with the provided password
if err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)); err != nil {
c.Header("WWW-Authenticate", "Basic realm=API Authorization")
c.AbortWithStatus(http.StatusUnauthorized)
return
}
c.Set(gin.AuthUserKey, username)
c.Next()
}
}
This example demonstrates:
- Database connectivity for user validation
- Using bcrypt for securely comparing passwords
- Protecting specific API routes while keeping others public
Security Considerations
When implementing Basic Authentication, keep these security considerations in mind:
- Always use HTTPS: Basic Auth sends credentials encoded (not encrypted), making them vulnerable without TLS.
- Store passwords securely: Never store plain text passwords. Use bcrypt or another secure hashing algorithm.
- Rate limiting: Implement rate limiting to prevent brute force attacks.
- Proper error messages: Don't reveal whether a username exists in error messages.
- Consider more robust auth: For production applications, consider OAuth2, JWT, or session-based authentication for more security.
Example: Adding Rate Limiting to Basic Auth
Here's how you could enhance security by adding a simple rate limiting mechanism:
package main
import (
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
"net/http"
"sync"
"time"
)
// Simple in-memory rate limiter
type IPRateLimiter struct {
ips map[string]*rate.Limiter
mu *sync.RWMutex
r rate.Limit
b int
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
return &IPRateLimiter{
ips: make(map[string]*rate.Limiter),
mu: &sync.RWMutex{},
r: r,
b: b,
}
}
func (i *IPRateLimiter) getLimiter(ip string) *rate.Limiter {
i.mu.RLock()
limiter, exists := i.ips[ip]
i.mu.RUnlock()
if !exists {
i.mu.Lock()
limiter = rate.NewLimiter(i.r, i.b)
i.ips[ip] = limiter
i.mu.Unlock()
}
return limiter
}
func main() {
r := gin.Default()
// Create a rate limiter: 3 requests per minute
limiter := NewIPRateLimiter(rate.Every(time.Minute/3), 1)
// Auth with rate limiting
auth := gin.Accounts{"admin": "password123"}
protected := r.Group("/protected")
// Apply rate limiting first
protected.Use(func(c *gin.Context) {
ip := c.ClientIP()
limiter := limiter.getLimiter(ip)
if !limiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests,
gin.H{"error": "Rate limit exceeded. Try again later."})
return
}
c.Next()
})
// Then apply authentication
protected.Use(gin.BasicAuth(auth))
protected.GET("/resource", func(c *gin.Context) {
user := c.MustGet(gin.AuthUserKey).(string)
c.JSON(http.StatusOK, gin.H{"message": "Success", "user": user})
})
r.Run(":8080")
}
This example limits authentication attempts to 3 per minute per IP address, which helps protect against brute force attacks.
Summary
Basic Authentication in Gin provides a simple way to protect routes in your web applications. While easy to implement, it should be used with additional security measures like HTTPS, secure password storage, and rate limiting.
Remember these key points:
- Use Gin's built-in
gin.BasicAuth()
middleware for quick implementation - Create custom authentication middleware for complex scenarios
- Always transmit Basic Auth credentials over HTTPS
- Consider using more robust authentication methods for production applications
Additional Resources and Exercises
Resources
Exercises
-
Multi-tier Access Control: Extend the Basic Authentication example to support different access levels (e.g., user, editor, admin) and restrict certain routes based on user roles.
-
Auth Time Limitations: Implement a system where credentials are only valid for a certain period, requiring re-authentication after expiry.
-
Logging Failed Authentication Attempts: Create a middleware that logs failed authentication attempts, including IP address and timestamp.
-
Authentication Integration: Combine Basic Authentication with another authentication method like JWT for a more robust solution.
-
Custom Authentication UI: Create a custom HTML form for Basic Authentication instead of relying on the browser's default authentication dialog.
By mastering Basic Authentication in Gin, you've taken an important step in building secure web applications. While basic auth has its limitations, understanding these fundamentals will help you implement more complex authentication systems in the future.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)