mcpField
E
encore-go-auth

encore-go-auth

github.com/encoredev安全30571 次安装2 天前更新
代码仓库

关于 encore-go-auth

为使用 Encore Go 的开发者提供身份验证实现指南,涵盖认证处理器、受保护端点及服务间身份传递。

主要功能

  1. 01定义认证处理器及返回值
  2. 02保护 API 端点
  3. 03从请求头、Cookie 和查询参数提取凭据
  4. 04在服务调用间传播认证数据
  5. 05在测试中覆盖认证数据

适用场景

  1. 01使用 JWT、API 密钥或 Cookie 验证请求
  2. 02限制资料等 API 仅供已认证用户访问
  3. 03测试需要特定用户身份的端点

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/encoredev/skills --skill encore-go-auth

安装后在 Claude Code 中输入/encore-go-auth确认已加载。

详细介绍

Encore Go 身份验证

说明

Encore Go 使用 //encore:authhandler 注解提供内置身份验证系统。

1. 创建身份验证处理程序
package auth

import (
    "context"
    "encore.dev/beta/auth"
    "encore.dev/beta/errs"
)

// AuthParams defines what the auth handler receives
type AuthParams struct {
    Authorization string `header:"Authorization"`
}

// AuthData defines what authenticated requests have access to
type AuthData struct {
    UserID string
    Email  string
    Role   string
}

//encore:authhandler
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
    token := strings.TrimPrefix(params.Authorization, "Bearer ")
    
    payload, err := verifyToken(token)
    if err != nil {
        return "", nil, &errs.Error{
            Code:    errs.Unauthenticated,
            Message: "invalid token",
        }
    }
    
    return auth.UID(payload.UserID), &AuthData{
        UserID: payload.UserID,
        Email:  payload.Email,
        Role:   payload.Role,
    }, nil
}
2. 保护端点
package user

import "context"

// Protected endpoint - requires authentication
//encore:api auth method=GET path=/profile
func GetProfile(ctx context.Context) (*Profile, error) {
    // Only authenticated users reach here
}

// Public endpoint - no authentication required
//encore:api public method=GET path=/health
func Health(ctx context.Context) (*HealthResponse, error) {
    return &HealthResponse{Status: "ok"}, nil
}
3. 在端点中访问身份验证数据
package user

import (
    "context"
    "encore.dev/beta/auth"
    myauth "myapp/auth"  // Import your auth package
)

//encore:api auth method=GET path=/profile
func GetProfile(ctx context.Context) (*Profile, error) {
    // Get the user ID
    userID, ok := auth.UserID()
    if !ok {
        // Should not happen with auth endpoint
    }
    
    // Get full auth data
    data := auth.Data().(*myauth.AuthData)
    
    return &Profile{
        UserID: string(userID),
        Email:  data.Email,
        Role:   data.Role,
    }, nil
}

身份验证处理程序签名

身份验证处理程序必须:

  1. 使用 //encore:authhandler 注解
  2. 接受 context.Context 和一个参数结构体指针
  3. 返回 (auth.UID, *YourAuthData, error)
//encore:authhandler
func MyAuthHandler(ctx context.Context, params *Params) (auth.UID, *AuthData, error)

身份验证处理程序的行为

场景返回值结果
凭据有效(uid, data, nil)请求通过身份验证
凭据无效("", nil, err),其中包含 errs.Unauthenticated401 响应
其他错误("", nil, err)请求中止

常见身份验证模式

JWT 令牌验证
import "github.com/golang-jwt/jwt/v5"

var secrets struct {
    JWTSecret string
}

func verifyToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(t *jwt.Token) (interface{}, error) {
        return []byte(secrets.JWTSecret), nil
    })
    if err != nil {
        return nil, err
    }
    
    claims, ok := token.Claims.(*Claims)
    if !ok || !token.Valid {
        return nil, errors.New("invalid token")
    }
    
    return claims, nil
}
API 密钥身份验证
//encore:authhandler
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
    apiKey := params.Authorization
    
    user, err := db.QueryRow[User](ctx, `
        SELECT id, email, role FROM users WHERE api_key = $1
    `, apiKey)
    if err != nil {
        return "", nil, &errs.Error{
            Code:    errs.Unauthenticated,
            Message: "invalid API key",
        }
    }
    
    return auth.UID(user.ID), &AuthData{
        UserID: user.ID,
        Email:  user.Email,
        Role:   user.Role,
    }, nil
}
基于 Cookie 的身份验证
type AuthParams struct {
    Cookie string `header:"Cookie"`
}

//encore:authhandler
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
    sessionID := parseCookie(params.Cookie, "session")
    if sessionID == "" {
        return "", nil, &errs.Error{
            Code:    errs.Unauthenticated,
            Message: "no session",
        }
    }

    session, err := getSession(ctx, sessionID)
    if err != nil || session.ExpiresAt.Before(time.Now()) {
        return "", nil, &errs.Error{
            Code:    errs.Unauthenticated,
            Message: "session expired",
        }
    }

    return auth.UID(session.UserID), &AuthData{
        UserID: session.UserID,
        Email:  session.Email,
        Role:   session.Role,
    }, nil
}
多来源身份验证(Cookie + Header + Query)

身份验证参数可以从多个来源提取数据:

import "net/http"

type AuthParams struct {
    SessionCookie *http.Cookie `cookie:"session"`       // From cookie
    Authorization string       `header:"Authorization"` // From header
    ClientID      string       `query:"client_id"`      // From query string
}

//encore:authhandler
func Authenticate(ctx context.Context, params *AuthParams) (auth.UID, *AuthData, error) {
    // Try session cookie first
    if params.SessionCookie != nil {
        return authenticateWithSession(ctx, params.SessionCookie.Value)
    }

    // Fall back to Authorization header
    if params.Authorization != "" {
        return authenticateWithToken(ctx, params.Authorization)
    }

    return "", nil, &errs.Error{
        Code:    errs.Unauthenticated,
        Message: "no credentials provided",
    }
}

服务间身份验证

身份验证数据会在内部服务调用中自动传递:

package order

import (
    "context"
    "myapp/user"  // Import the user service
)

//encore:api auth method=GET path=/orders/:id
func GetOrderWithUser(ctx context.Context, params *GetOrderParams) (*OrderWithUser, error) {
    order, err := getOrder(ctx, params.ID)
    if err != nil {
        return nil, err
    }
    
    // Auth is automatically propagated to this call
    profile, err := user.GetProfile(ctx)
    if err != nil {
        return nil, err
    }
    
    return &OrderWithUser{Order: order, User: profile}, nil
}

使用身份验证进行测试

使用 auth.WithContext 在测试中覆盖身份验证数据:

package user_test

import (
    "context"
    "testing"

    "encore.dev/beta/auth"
    myauth "myapp/auth"
    "myapp/user"
)

func TestGetProfile(t *testing.T) {
    // Create a context with auth data
    ctx := auth.WithContext(
        context.Background(),
        auth.UID("test-user-123"),
        &myauth.AuthData{
            UserID: "test-user-123",
            Email:  "[email protected]",
            Role:   "user",
        },
    )

    // Call the endpoint with the authenticated context
    profile, err := user.GetProfile(ctx)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }

    if profile.Email != "[email protected]" {
        t.Errorf("expected [email protected], got %s", profile.Email)
    }
}

指南

  • 每个应用程序只能有一个 //encore:authhandler
  • 将 auth.UID 作为第一个返回值(用户标识符)
  • 将自定义 AuthData 结构体作为第二个返回值
  • 使用 auth.UserID() 获取通过身份验证的用户 ID
  • 使用 auth.Data() 并进行类型断言,以获取完整的身份验证数据
  • 身份验证会在服务间调用中自动传递
  • 使用 auth.WithContext() 在测试中覆盖身份验证
  • 保持身份验证处理程序快速——每个经过身份验证的请求都会运行它

评论

支持 Markdown