mcpField
E
encore-go-auth

encore-go-auth

github.com/encoredevセキュリティ30571 回インストール2 日前に更新
リポジトリ

encore-go-auth について

Encore Go アプリケーションで認証を実装するためのガイドです。認証ハンドラー、保護されたエンドポイント、サービス間の認証データ伝播を扱います。

主な機能

  1. 01認証ハンドラーと戻り値の定義
  2. 02API エンドポイントの保護
  3. 03ヘッダー、Cookie、クエリパラメーターからの認証情報取得
  4. 04サービス間での認証データ伝播
  5. 05テストでの認証データの上書き

活用シーン

  1. 01JWT、API キー、Cookie によるリクエスト認証
  2. 02プロフィールなどのエンドポイントへの認証要件の設定
  3. 03任意のユーザー ID を使ったエンドポイントのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$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 と params 構造体のポインタを受け取る
  3. (auth.UID, *YourAuthData, error) を返す
//encore:authhandler
func MyAuthHandler(ctx context.Context, params *Params) (auth.UID, *AuthData, error)

認証ハンドラーの動作

シナリオ戻り値結果
有効な認証情報(uid, data, nil)リクエストが認証される
無効な認証情報errs.Unauthenticated を伴う ("", nil, err)401 レスポンス
その他のエラー("", 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 + ヘッダー + クエリ)

認証パラメータは複数のソースからデータを抽出できます:

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 は 1 つだけです
  • 最初の戻り値として auth.UID (ユーザー識別子) を返します
  • 2 番目の値としてカスタムの AuthData 構造体を返します
  • 認証されたユーザー ID を取得するには auth.UserID() を使用します
  • 完全な認証データを取得するには auth.Data() を使用し、型アサーションを行います
  • 認証はサービス間呼び出しで自動的に伝播されます
  • テストで認証を上書きするには auth.WithContext() を使用します
  • 認証ハンドラーは高速に保ちます - 認証されたすべてのリクエストで実行されます

コメント