Encore アプリケーションで Logto を使用する方法

> 本記事は元々 [blog.logto.io](https://blog.logto.io/ja/encore-with-logto) に掲載されたものです。

[Encore](https://encore.dev) は、製品レベルの API やマイクロサービスを簡単に構築できるバックエンド開発プラットフォームです。

[Logto](https://logto.io) は、モダンな Auth0 の代替で、数分でサインイン体験とユーザーアイデンティティを構築するのに役立ちます。特に Encore で構築された API サービスを保護するのに適しています。

このガイドでは、Encore アプリケーションと Logto を統合して、安全なユーザー認証を実装し、API エンドポイントを保護する方法を示します。

<Note>
このガイドを開始する前に、[Encore クイックスタートガイド](https://encore.dev/docs/go/quick-start)に従って、初期の Encore アプリケーションをセットアップしてください。このガイドは、基本的な Encore アプリケーションを基に構築されています。
</Note>

## Logto の設定

Encore と統合を始める前に、Logto でいくつかの設定を行う必要があります：

1. [Logto Cloud](https://cloud.logto.io) でアカウントを作成します（まだお持ちでない場合）。

2. Logto Console で API リソースを作成します。これはあなたの Encore API サービスを表します。
   - Logto Console の「API Resources」に移動し、新しい API を作成します。
   - 名前と API 識別子を設定します（例：`https://api.encoreapp.com`）。
   - API リソース詳細ページで API 識別子をメモします。後で必要になります。

  ![Logto API リソース](https://uploads.strapi.logto.io/logto_api_resource_76d730d556.png)

3. フロントエンドアプリケーションのためにアプリケーションを作成します。
   - Logto Console の「Applications」に移動します。
   - お使いのフロントエンドフレームワークに応じて新しいアプリケーションを作成します（例として React を使用していますが、任意のシングルページアプリケーション (SPA) またはネイティブアプリを作成できます）。
   - （オプションで、後ほど説明します）Logto をフロントエンドアプリケーションと統合します。Logto Console のガイドに従ってください。
   - アプリケーション詳細ページでアプリケーション ID と発行者 URL をメモします。これらも後で必要になります。

  ![Logto アプリケーションエンドポイント](https://uploads.strapi.logto.io/logto_application_endpoints_832eddc545.png)

## Encore API サービスの認証ハンドラーの設定

次に、Encore アプリケーションで認証を実装します。Encore の組み込み [auth handler](https://encore.dev/docs/go/develop/auth) を使用して、Logto の JWT トークンを検証します。

Encore アプリケーションに以下の2つのモジュールを追加します：

```shell
$ go get github.com/golang-jwt/jwt/v5
$ go get github.com/MicahParks/keyfunc/v3
```

`auth/auth.go` を作成し、以下のコードを追加します：

```go
package auth

import (
	"context"
	"time"

	"encore.dev/beta/auth"
	"encore.dev/beta/errs"
	"github.com/MicahParks/keyfunc/v3"
	"github.com/golang-jwt/jwt/v5"
)

// 認証用の設定変数
var (
  // JWT の期待されるオーディエンス、Logto API リソースの詳細ページから
	apiResourceIndicator = "<your-logto-api-resource-identifier>"
	// 発行者 URL、Logto アプリケーションの詳細ページから
	issuer = "<your-logto-issuer-url>"
	// JSON Web Key Set (JWKS) を取得するための URL
	jwksUri = issuer + "/jwks"
	// トークンクレーム中の期待されるクライアント ID、Logto アプリケーションの詳細ページから
	clientId = "<your-logto-application-id>"
)

// RequiredClaims は JWT クレームの期待される構造を定義します
// 標準の JWT クレームにカスタム ClientID フィールドを拡張します
type RequiredClaims struct {
	ClientID string `json:"client_id"`
	jwt.RegisteredClaims
}

// AuthHandler は JWT トークンを検証し、ユーザー ID を抽出します
// Encore の認証ハンドラーインターフェースを実装します
//
//encore:authhandler
func AuthHandler(ctx context.Context, token string) (auth.UID, error) {
	// ID プロバイダーから JWKS (JSON Web Key Set) を取得して解析します
	jwks, err := keyfunc.NewDefaultCtx(ctx, []string{jwksUri})
	if err != nil {
		return "", &errs.Error{
			Code:    errs.Internal,
			Message: "failed to fetch JWKS",
		}
	}

	// 必要なクレームと検証オプションで JWT トークンを解析して検証します
	parsedToken, err := jwt.ParseWithClaims(
		token,
		&RequiredClaims{},
		jwks.Keyfunc,
		// この API リソースを意図したトークンであることを期待します
		jwt.WithAudience(apiResourceIndicator),
		// この発行者によって発行されたトークンであることを期待します
		jwt.WithIssuer(issuer),
		// クロックスキューに対する余裕を許可します
		jwt.WithLeeway(time.Minute*10),
	)

	// トークン解析中にエラーがあったかどうかを確認します
	if err != nil {
		return "", &errs.Error{
			Code:    errs.Unauthenticated,
			Message: "invalid token",
		}
	}

	// トークン中のクライアント ID が期待されるクライアント ID と一致することを検証します
	if parsedToken.Claims.(*RequiredClaims).ClientID != clientId {
		return "", &errs.Error{
			Code:    errs.Unauthenticated,
			Message: "invalid token",
		}
	}

	// トークンクレームからユーザー ID (サブジェクト) を抽出します
	userId, err := parsedToken.Claims.GetSubject()
	if err != nil {
		return "", &errs.Error{
			Code:    errs.Unauthenticated,
			Message: "invalid token",
		}
	}

	// ユーザー ID を Encore auth.UID として返します
	return auth.UID(userId), nil
}
```

そして、この認証ハンドラーを使って API エンドポイントを保護できます：

```go
package hello

import (
	"context"
	"fmt"

	"encore.dev/beta/auth"
	"encore.dev/beta/errs"
)

//encore:api auth method=GET path=/hello
func Hello(ctx context.Context) (*Response, error) {
	userId, hasUserId := auth.UserID()

	if !hasUserId {
		return nil, &errs.Error{
			Code:    errs.Internal,
			Message: "expect user ID",
		}
	}

	return &Response{Message: fmt.Sprintf("Hello, %s!", string(userId))}, nil
}

type Response struct {
	Message string
}
```

## フロントエンド

Encore API サービスでの作業が完了しました。次に、フロントエンドアプリケーションで Logto を統合する必要があります。

[Logto Quick start](https://docs.logto.io/quick-starts) ページで使用しているフレームワークを選択し、フロントエンドアプリケーションで Logto を統合します。このガイドでは React を例として使用します。

[React アプリケーションへの認証追加](https://docs.logto.io/quick-starts/react) ガイドを参照し、React アプリケーションで Logto を統合する方法を学びます。この例では、Integration セクションまでを実行するだけで結構です。その後、フロントエンドアプリケーションが Logto から Encore API へのアクセス トークンを取得する方法を示します。

まず、Encore アプリで使用される API リソースを `resources` フィールドに追加して `LogtoConfig` を更新します。これは、この API リソース (Encore API) のためにアクセストークンを要求することを Logto に伝えます。

```ts
import { LogtoConfig } from '@logto/react';

const config: LogtoConfig = {
  // ...他の設定
  resources: ['https://api.encoreapp.com'],
};
```

`LogtoConfig` を更新した後、ユーザーが既にサインインしている場合、新しい `LogtoConfig` 設定が有効になるようにサインアウトして再度サインインする必要があります。

ユーザーがログインすると、Logto React SDK が提供する `getAccessToken` メソッドを使用して、特定の API リソースへのアクセス トークンを取得できます。例えば、Encore API にアクセスするには、`https://api.encoreapp.com` を API リソース 識別子として使用します。

その後、このアクセス トークンをリクエスト ヘッダーの `Authorization` フィールドに追加し、後続のリクエストで使用します。

```ts
const { getAccessToken } = useLogto();
const accessToken = await getAccessToken('https://api.encoreapp.com');

// このアクセス トークンを リクエスト ヘッダーの `Authorization` フィールドに追加し、後続のリクエストで使用します
fetch('https://your-encore-api-endpoint/hello', {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});
```

これで、Encore アプリケーションとの Logto の統合が正常に完了しました。

## もっと探求する

Encore でさらに多くの Logto 機能を使用したい場合は、次のリンクを参照してください：

- Logto の [カスタム トークン クレーム](https://docs.logto.io/developers/custom-token-claims) を組み合わせて、認証ハンドラーに [カスタム ユーザー データ](https://encore.dev/docs/go/develop/auth#with-custom-user-data) を設定する
- [Logto RBAC 機能](https://docs.logto.io/authorization/role-based-access-control) を利用して、Encore API サービスに承認サポートを追加します。React 統合チュートリアルは、`scope` 情報をあなたのアクセス トークンに追加する方法も示しています（Logto 設定を更新した後に再度サインインする必要があることに注意してください）
- [Build a multi-tenant SaaS application with Logto](https://blog.logto.io/build-multi-tenant-saas-application) を参照し、組織トークンを使用して Encore API サービスでマルチテナントアプリケーションを構築する