Skip to main content

Documentation Index

Fetch the complete documentation index at: https://unkey.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

This recipe shows how to create robust API key authentication middleware for the Echo web framework.

Complete Middleware Implementation

package main

import (
    "net/http"
    "os"
    "slices"
    "strings"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    unkey "github.com/unkeyed/sdks/api/go/v2"
    "github.com/unkeyed/sdks/api/go/v2/models/components"
)

var unkeyClient *unkey.Unkey

func init() {
    unkeyClient = unkey.New(
        unkey.WithSecurity(os.Getenv("UNKEY_ROOT_KEY")),
    )
}

// UnkeyAuthMiddleware creates an Echo middleware for API key verification
func UnkeyAuthMiddleware() echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            authHeader := c.Request().Header.Get("Authorization")
            if authHeader == "" {
                return c.JSON(http.StatusUnauthorized, map[string]string{
                    "error": "Missing Authorization header",
                    "code":  "MISSING_KEY",
                })
            }

            apiKey := strings.TrimPrefix(authHeader, "Bearer ")

            // Verify with Unkey
            res, err := unkeyClient.Keys.VerifyKey(c.Request().Context(), components.V2KeysVerifyKeyRequestBody{
                Key: apiKey,
            })

            if err != nil {
                return c.JSON(http.StatusServiceUnavailable, map[string]string{
                    "error":   "Verification service unavailable",
                    "code":    "SERVICE_ERROR",
                    "message": err.Error(),
                })
            }

            if !res.V2KeysVerifyKeyResponseBody.Data.Valid {
                code := string(res.V2KeysVerifyKeyResponseBody.Data.Code)

                return c.JSON(http.StatusUnauthorized, map[string]string{
                    "error": "Invalid API key",
                    "code":  code,
                })
            }

            // Store verification result in context
            c.Set("unkey", &res.V2KeysVerifyKeyResponseBody.Data)
            return next(c)
        }
    }
}

// RequirePermission creates middleware to check specific permissions
func RequirePermission(permission string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            result := c.Get("unkey")
            if result == nil {
                return c.JSON(http.StatusUnauthorized, map[string]string{
                    "error": "Authentication required",
                    "code":  "AUTH_REQUIRED",
                })
            }

            keyResult, ok := result.(*components.V2KeysVerifyKeyResponseData)
            if !ok || keyResult == nil {
                return c.JSON(http.StatusInternalServerError, map[string]any{
                    "error": "Invalid authentication context",
                    "code":  "INTERNAL_ERROR",
                })
            }

            if slices.Contains(keyResult.Permissions, permission) {
                return next(c)
            }

            return c.JSON(http.StatusForbidden, map[string]any{
                "error":    "Insufficient permissions",
                "code":     "FORBIDDEN",
                "required": permission,
            })
        }
    }
}

// RequireRole creates middleware to check specific roles
func RequireRole(role string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            result := c.Get("unkey")
            if result == nil {
                return c.JSON(http.StatusUnauthorized, map[string]string{
                    "error": "Authentication required",
                    "code":  "AUTH_REQUIRED",
                })
            }

            keyResult, ok := result.(*components.V2KeysVerifyKeyResponseData)
            if !ok || keyResult == nil {
                return c.JSON(http.StatusInternalServerError, map[string]any{
                    "error": "Invalid authentication context",
                    "code":  "INTERNAL_ERROR",
                })
            }

            if slices.Contains(keyResult.Roles, role) {
                return next(c)
            }

            return c.JSON(http.StatusForbidden, map[string]any{
                "error":    "Insufficient role",
                "code":     "FORBIDDEN",
                "required": role,
            })
        }
    }
}

// GetUnkeyResult retrieves the Unkey verification result from context
func GetUnkeyResult(c echo.Context) *components.V2KeysVerifyKeyResponseData {
    result := c.Get("unkey")
    if result == nil {
        return nil
    }
    r, ok := result.(*components.V2KeysVerifyKeyResponseData)
    if !ok {
        return nil
    }
    return r
}

// Usage example
func main() {
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // Public routes
    e.GET("/health", func(c echo.Context) error {
        return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
    })

    // Protected routes
    api := e.Group("/api")
    api.Use(UnkeyAuthMiddleware())
    {
        api.GET("/data", func(c echo.Context) error {
            result := GetUnkeyResult(c)
            ownerID := ""
            if result.Identity != nil {
                ownerID = result.Identity.ExternalID
            }
            return c.JSON(http.StatusOK, map[string]any{
                "message": "Access granted",
                "key_id":  *result.KeyID,
                "owner":   ownerID,
                "meta":    result.Meta,
            })
        })

        api.GET("/profile", func(c echo.Context) error {
            result := GetUnkeyResult(c)
            keyID := ""
            if result.KeyID != nil {
                keyID = *result.KeyID
            }
            return c.JSON(http.StatusOK, map[string]any{
                "key_id":      keyID,
                "permissions": result.Permissions,
                "roles":       result.Roles,
            })
        })
    }

    // Admin routes
    admin := e.Group("/api/admin")
    admin.Use(UnkeyAuthMiddleware(), RequirePermission("admin:read"))
    {
        admin.GET("/users", func(c echo.Context) error {
            return c.JSON(http.StatusOK, map[string]any{
                "message": "Admin access granted",
                "users":   []string{"user1", "user2"},
            })
        })

        admin.POST("/config", func(c echo.Context) error {
            return c.JSON(http.StatusOK, map[string]string{
                "message": "Config updated",
            })
        }, RequirePermission("admin:write"))
    }

    e.Start(":8080")
}

Custom Configuration

Create configurable middleware:
type AuthConfig struct {
    HeaderName string
    Prefix     string
    Optional   bool
}

func UnkeyAuthWithConfig(config AuthConfig) echo.MiddlewareFunc {
    if config.HeaderName == "" {
        config.HeaderName = "Authorization"
    }
    if config.Prefix == "" {
        config.Prefix = "Bearer "
    }

    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            authHeader := c.Request().Header.Get(config.HeaderName)

            if authHeader == "" {
                if config.Optional {
                    return next(c)
                }
                return c.JSON(http.StatusUnauthorized, map[string]string{
                    "error": "Missing API key",
                    "code":  "MISSING_KEY",
                })
            }

            apiKey := strings.TrimPrefix(authHeader, config.Prefix)

            res, err := unkeyClient.Keys.VerifyKey(c.Request().Context(), components.V2KeysVerifyKeyRequestBody{
                Key: apiKey,
            })

            if err != nil {
                return c.JSON(http.StatusServiceUnavailable, map[string]string{
                    "error": "Verification failed",
                })
            }

            if !res.V2KeysVerifyKeyResponseBody.Data.Valid {
                return c.JSON(http.StatusUnauthorized, map[string]string{
                    "error": "Invalid API key",
                })
            }

            c.Set("unkey", &res.V2KeysVerifyKeyResponseBody.Data)
            return next(c)
        }
    }
}

// Usage with custom config
e.GET("/api/public", func(c echo.Context) error {
    result := GetUnkeyResult(c)
    if result != nil {
        keyID := ""
        if result.KeyID != nil {
            keyID = *result.KeyID
        }
        return c.JSON(http.StatusOK, map[string]any{
            "message": "Authenticated",
            "key_id":  keyID,
        })
    }
    return c.JSON(http.StatusOK, map[string]string{
        "message": "Anonymous",
    })
}, UnkeyAuthWithConfig(AuthConfig{Optional: true}))

Group-Level Middleware

Apply middleware to route groups:
// API v1 routes
v1 := e.Group("/api/v1")
v1.Use(UnkeyAuthMiddleware())

// Public subset
public := v1.Group("/public")
public.GET("/status", statusHandler)

// Protected subset
protected := v1.Group("/protected")
protected.Use(RequirePermission("data:read"))
protected.GET("/data", dataHandler)

Testing

# Test without key
curl http://localhost:8080/api/data
# {"error":"Missing Authorization header","code":"MISSING_KEY"}

# Test with valid key
curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:8080/api/data
# {"message":"Access granted","key_id":"key_..."}

# Test admin route without permission
curl -H "Authorization: Bearer USER_KEY" http://localhost:8080/api/admin/users
# {"error":"Insufficient permissions","code":"FORBIDDEN"}

# Test admin route with permission
curl -H "Authorization: Bearer ADMIN_KEY" http://localhost:8080/api/admin/users
# {"message":"Admin access granted","users":["user1","user2"]}

Go Quickstart

Get started with Go and Unkey

Go SDK Reference

Complete Go SDK documentation
Last modified on April 7, 2026