encore-go-auth

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Encore Go Authentication

Encore Go 身份验证

Instructions

操作步骤

Encore Go provides a built-in authentication system using the
//encore:authhandler
annotation.
Encore Go 提供了一个内置的身份验证系统,使用
//encore:authhandler
注解。

1. Create an Auth Handler

1. 创建身份验证处理器

go
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
}
go
package auth

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

// AuthParams 定义了身份验证处理器接收的参数
type AuthParams struct {
    Authorization string `header:"Authorization"`
}

// AuthData 定义了已认证请求可访问的数据
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. Protect Endpoints

2. 保护接口端点

go
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
}
go
package user

import "context"

// 受保护的端点 - 需要身份验证
//encore:api auth method=GET path=/profile
func GetProfile(ctx context.Context) (*Profile, error) {
    // 只有已认证用户能到达这里
}

// 公开端点 - 无需身份验证
//encore:api public method=GET path=/health
func Health(ctx context.Context) (*HealthResponse, error) {
    return &HealthResponse{Status: "ok"}, nil
}

3. Access Auth Data in Endpoints

3. 在端点中访问身份验证数据

go
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
}
go
package user

import (
    "context"
    "encore.dev/beta/auth"
    myauth "myapp/auth"  // 导入你的身份验证包
)

//encore:api auth method=GET path=/profile
func GetProfile(ctx context.Context) (*Profile, error) {
    // 获取用户ID
    userID, ok := auth.UserID()
    if !ok {
        // 对于需要身份验证的端点,这种情况不应该发生
    }
    
    // 获取完整的身份验证数据
    data := auth.Data().(*myauth.AuthData)
    
    return &Profile{
        UserID: string(userID),
        Email:  data.Email,
        Role:   data.Role,
    }, nil
}

Auth Handler Signature

身份验证处理器签名

The auth handler must:
  1. Have the
    //encore:authhandler
    annotation
  2. Accept
    context.Context
    and a params struct pointer
  3. Return
    (auth.UID, *YourAuthData, error)
go
//encore:authhandler
func MyAuthHandler(ctx context.Context, params *Params) (auth.UID, *AuthData, error)
身份验证处理器必须满足:
  1. 带有
    //encore:authhandler
    注解
  2. 接收
    context.Context
    和一个参数结构体指针
  3. 返回
    (auth.UID, *YourAuthData, error)
go
//encore:authhandler
func MyAuthHandler(ctx context.Context, params *Params) (auth.UID, *AuthData, error)

Auth Handler Behavior

身份验证处理器行为

ScenarioReturnsResult
Valid credentials
(uid, data, nil)
Request authenticated
Invalid credentials
("", nil, err)
with
errs.Unauthenticated
401 response
Other error
("", nil, err)
Request aborted
场景返回值结果
有效凭据
(uid, data, nil)
请求已认证
无效凭据
("", nil, err)
errs.Unauthenticated
返回401响应
其他错误
("", nil, err)
请求被中止

Common Auth Patterns

常见身份验证模式

JWT Token Validation

JWT令牌验证

go
import (
    "github.com/golang-jwt/jwt/v5"
    "encore.dev/config"
)

var secrets struct {
    JWTSecret config.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
}
go
import (
    "github.com/golang-jwt/jwt/v5"
    "encore.dev/config"
)

var secrets struct {
    JWTSecret config.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 Key Authentication

API密钥身份验证

go
//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
}
go
//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-Based Auth

基于Cookie的身份验证

go
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
}
go
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
}

Service-to-Service Auth

服务间身份验证

Auth data automatically propagates in internal service calls:
go
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
}
身份验证数据会在内部服务调用中自动传播:
go
package order

import (
    "context"
    "myapp/user"  // 导入用户服务
)

//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
    }
    
    // 身份验证会自动传播到这个调用中
    profile, err := user.GetProfile(ctx)
    if err != nil {
        return nil, err
    }
    
    return &OrderWithUser{Order: order, User: profile}, nil
}

Guidelines

注意事项

  • Only one
    //encore:authhandler
    per application
  • Return
    auth.UID
    as the first return value (user identifier)
  • Return your custom
    AuthData
    struct as second value
  • Use
    auth.UserID()
    to get the authenticated user ID
  • Use
    auth.Data()
    and type assert to get full auth data
  • Auth propagates automatically in service-to-service calls
  • Keep auth handlers fast - they run on every authenticated request
  • 每个应用只能有一个
    //encore:authhandler
  • 第一个返回值必须是
    auth.UID
    (用户标识符)
  • 第二个返回值是自定义的
    AuthData
    结构体
  • 使用
    auth.UserID()
    获取已认证用户的ID
  • 使用
    auth.Data()
    并进行类型断言来获取完整的身份验证数据
  • 身份验证会在服务间调用中自动传播
  • 保持身份验证处理器的执行速度——它们会在每个已认证请求中运行