Skip to main content

Echo Basic Authentication

Introduction

Basic Authentication is one of the simplest authentication mechanisms defined in the HTTP protocol. Despite its simplicity, it's widely used for API authentication in many applications. In this tutorial, we'll explore how to implement Basic Authentication in the Echo web framework, a high-performance, extensible, and minimalist Go web framework.

Basic Authentication works by sending credentials (username and password) encoded in the HTTP request header. The server validates these credentials before granting access to protected resources. While not the most secure method when used alone (as credentials are only base64 encoded, not encrypted), it can be effective when used over HTTPS.

Understanding Basic Authentication

Before we dive into the implementation, let's understand how Basic Authentication works:

  1. The client makes a request to a protected resource without authentication
  2. The server responds with a 401 Unauthorized status and includes a WWW-Authenticate: Basic header
  3. The browser prompts the user for credentials
  4. The browser sends the credentials in the format username:password encoded with Base64 in the Authorization header
  5. The server decodes the credentials, verifies them, and grants access if valid

Implementing Basic Authentication in Echo

Echo makes it easy to implement Basic Authentication through middleware. Let's see how to use it:

Basic Implementation

go
package main

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"
)

func main() {
// Create a new Echo instance
e := echo.New()

// Define the authentication validator function
basicAuth := middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
// Check if username and password are correct
// This is where you would typically check against a database
if username == "john" && password == "secret" {
return true, nil
}
return false, nil
})

// Public route
e.GET("/public", func(c echo.Context) error {
return c.String(http.StatusOK, "This is a public endpoint")
})

// Restricted group
r := e.Group("/restricted")

// Apply the middleware to the restricted group
r.Use(basicAuth)

// Protected route
r.GET("/profile", func(c echo.Context) error {
return c.String(http.StatusOK, "Welcome to your profile page!")
})

// Start the server
e.Logger.Fatal(e.Start(":8080"))
}

In this example:

  • We create a basic authentication middleware using middleware.BasicAuth
  • We define a validation function that checks credentials
  • We apply the middleware to a group of routes that we want to protect
  • We leave some routes public by not applying the middleware to them

Running the Example

  1. Save the code as basic_auth.go
  2. Run it with go run basic_auth.go
  3. Open your browser and try accessing:
    • http://localhost:8080/public - Should work without authentication
    • http://localhost:8080/restricted/profile - Should prompt for username and password

Accessing User Information

Once a user is authenticated, you might want to access their information in the handler functions. Here's how you can access the authenticated username:

go
r.GET("/profile", func(c echo.Context) error {
// Get username from the context
user := c.Get("username").(string)
return c.String(http.StatusOK, "Welcome to your profile, "+user+"!")
})

To make this work, you need to modify your BasicAuth function to set the username in the context:

go
basicAuth := middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
if username == "john" && password == "secret" {
// Store username in context
c.Set("username", username)
return true, nil
}
return false, nil
})

Real-World Example: API with Database Authentication

In real applications, you'll typically validate credentials against a database. Here's a more realistic example:

go
package main

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"
"golang.org/x/crypto/bcrypt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)

// User represents a user from the database
type User struct {
ID int
Username string
Password string // Hashed password
}

// Global DB connection
var db *sql.DB

func main() {
// Initialize DB connection (error handling omitted for brevity)
db, _ = sql.Open("mysql", "user:password@/dbname")

e := echo.New()

// Define the authentication validator function
basicAuth := middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
// Get user from database
var user User
err := db.QueryRow("SELECT id, username, password FROM users WHERE username = ?", username).Scan(
&user.ID, &user.Username, &user.Password,
)

if err != nil {
return false, nil // User not found
}

// Compare hashed password with input
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
return false, nil // Password doesn't match
}

// Store user info in context for later use
c.Set("user", user)
return true, nil
})

// Public routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Welcome to our API")
})

// Protected API routes
api := e.Group("/api", basicAuth)

api.GET("/users/me", func(c echo.Context) error {
user := c.Get("user").(User)
return c.JSON(http.StatusOK, map[string]interface{}{
"id": user.ID,
"username": user.Username,
})
})

e.Logger.Fatal(e.Start(":8080"))
}

This example:

  1. Connects to a MySQL database
  2. Validates credentials against stored users
  3. Uses bcrypt for secure password comparison
  4. Stores the entire user object in the context for later use

Best Practices for Basic Authentication

  1. Always use HTTPS: Basic Authentication sends credentials encoded (not encrypted), so using HTTPS is essential to prevent man-in-the-middle attacks.

  2. Set proper realm: Set a descriptive realm to help users understand what credentials they should provide:

go
basicAuth := middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
Validator: func(username, password string, c echo.Context) (bool, error) {
// Validation logic here
},
Realm: "My Restricted Area", // This text may appear in the login prompt
})
  1. Use strong credentials: Avoid simple passwords and implement password complexity rules.

  2. Consider timeouts: Implement token expiration or session timeouts for better security.

  3. Rate limiting: Add rate limiting to prevent brute force attacks:

go
// Add rate limiting middleware
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
  1. Consider alternatives: For more complex applications, consider OAuth, JWT or session-based authentication instead of Basic Authentication.

Custom Error Response

You can customize the error response when authentication fails:

go
basicAuth := middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
Validator: func(username, password string, c echo.Context) (bool, error) {
// Validation logic here
},
ErrorHandler: func(c echo.Context, err error) error {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Please provide valid credentials",
})
},
})

Summary

Echo's Basic Authentication middleware provides a simple way to secure your web applications and APIs. In this tutorial, we learned:

  • How Basic Authentication works in HTTP
  • How to implement Basic Authentication in Echo
  • How to access authenticated user information
  • A real-world example with database authentication
  • Best practices for using Basic Authentication

While Basic Authentication is simple to implement, remember that it should be used over HTTPS and might not be suitable for all applications, especially those requiring more advanced authentication features.

Additional Resources

Exercises

  1. Implement Basic Authentication with a JSON file as the credential store
  2. Add logging for failed authentication attempts
  3. Combine Basic Authentication with another middleware (like CORS)
  4. Create an admin-only section with different credentials
  5. Implement a custom error response with a JSON structure that includes an error code and message

By completing these exercises, you'll gain a better understanding of Echo's authentication capabilities and how to adapt them to different scenarios.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)