Complete Middleware Implementation
Copy
Ask AI
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)
return c.JSON(http.StatusOK, map[string]any{
"key_id": *result.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:Copy
Ask AI
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 {
return c.JSON(http.StatusOK, map[string]any{
"message": "Authenticated",
"key_id": *result.KeyID,
})
}
return c.JSON(http.StatusOK, map[string]string{
"message": "Anonymous",
})
}, UnkeyAuthWithConfig(AuthConfig{Optional: true}))
Group-Level Middleware
Apply middleware to route groups:Copy
Ask AI
// 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
Copy
Ask AI
# 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"]}
Related
Go Quickstart
Get started with Go and Unkey
Go SDK Reference
Complete Go SDK documentation

