This recipe shows how to create robust API key authentication middleware for the Echo web framework.
Complete Middleware Implementation#
package mainimport ( "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.Unkeyfunc init() { unkeyClient = unkey.New( unkey.WithSecurity(os.Getenv("UNKEY_ROOT_KEY")), )}// UnkeyAuthMiddleware creates an Echo middleware for API key verificationfunc 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 permissionsfunc 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 rolesfunc 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 contextfunc 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 examplefunc 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) identityExternalID := "" if result.Identity != nil { identityExternalID = result.Identity.ExternalID } return c.JSON(http.StatusOK, map[string]any{ "message": "Access granted", "key_id": *result.KeyID, "identity_external_id": identityExternalID, "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 confige.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 routesv1 := e.Group("/api/v1")v1.Use(UnkeyAuthMiddleware())// Public subsetpublic := v1.Group("/public")public.GET("/status", statusHandler)// Protected subsetprotected := v1.Group("/protected")protected.Use(RequirePermission("data:read"))protected.GET("/data", dataHandler)Testing#
# Test without keycurl http://localhost:8080/api/data# {"error":"Missing Authorization header","code":"MISSING_KEY"}# Test with valid keycurl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:8080/api/data# {"message":"Access granted","key_id":"key_..."}# Test admin route without permissioncurl -H "Authorization: Bearer USER_KEY" http://localhost:8080/api/admin/users# {"error":"Insufficient permissions","code":"FORBIDDEN"}# Test admin route with permissioncurl -H "Authorization: Bearer ADMIN_KEY" http://localhost:8080/api/admin/users# {"message":"Admin access granted","users":["user1","user2"]}Related#
Go Quickstart
Get started with Go and Unkey
Go SDK Reference
Complete Go SDK documentation