マシン間通信のための API リソースのセキュリティを確保する
OAuth 2.0 と JWT を活用して、マシン間通信のための API リソースを保護する方法を学びます。
複数のサービスを含むプロジェクトを構築する場合、API リソースのセキュリティが重要な懸念事項です。この記事では、サービス間(マシン間)の通信を保護するために OAuth 2.0 と JWT を活用する方法と、最小特権の原則に従ってロールベースのアクセス制御 (RBAC) を適用する方法を紹介します。
始めましょう
以下の前提条件があると仮定します:
- Logto Cloud アカウント、または自己ホスト型 Logto インスタンス
- 互いに通信する必要がある少なくとも 2 つのサービス
デモンストレーションの目的で、次のサービスがあると仮定します:
- ショッピングカートを管理する API を提供するショッピングカートサービス
- エンドポイント:
https://cart.example.com/api
- エンドポイント:
- 決済を処理する API を提供する決済サービス
- エンドポイント:
https://payment.example.com/api
- エンドポイント:
認証フロー
今、私たちのカートサービスは決済を処理するために決済サービスを呼び出す必要があります。認証フローは次のようになります:
上の図のいくつかの重要な概念:
- JWT (RFC 7519): JSON ウェブトークン。JWT の紹介については、前回の記事 を参照.
- JWK (RFC 7517): JWT の署名を検証するために使用される JSON ウェブキー。JWK セットは JWK のセットです。
- "client_credentials" グラント (RFC 6749): OAuth 2.0 でのグラントタイプ。クライアントの資格情報を使用してアクセストークンを取得します。詳細は後のセクションで説明します。
上の図の各参加者は、認証フローで役割を持っています:
- カートサービス: 決済サービスを呼び出す必要があるクライアント。サービスであっても、OAuth 2.0 のコンテキストでは依然としてクライアントであり、Logto ではそのようなクライアントを「マシン間アプリケーション」と呼びます。
- Logto: アクセストークンを発行する OAuth 2.0 認可サーバー。
- 決済サービス: 決済を処理する API リソースを提供する。
認証フローをステップバイステップで進んでいきましょう。
初期セットアップ
認証フローを実行するには、Logto にマシン間アプリ (カートサービス) と API リソース (決済サービス) を作成する必要があります。
API リソースを作成
カートサービスが認証を実行する際に決済サービスの API を認識する必要があるため、最初に API リソースを作成する必要があります。Logto コンソールに移動し、左のサイドバーで API リソース をクリックし、API リソースを作成 をクリックします。開いたダイアログで、開始を支援するためのいくつかのチュートリアルを提供します。また、チュートリアルなしで続行 をクリックしてスキップすることもできます。
API 名と識別子を入力します。たとえば、決済サービス
と https://payment.example.com/api
を入力し、API リソースを作成 をクリックします。
API リソースを作成すると、詳細ページにリダイレクトされます。今のところそのままにしておいてもかまいません。
マシン間アプリを作成
左のサイドバーで アプリケーション をクリックし、アプリケーションを作成 をクリックします。開いたダイアログで、マシン間 カ ードを見つけて、構築を開始 をクリックします。
アプリケーション名を入力します。たとえば、カートサービス
と入力し、アプリケーションを作成 をクリックします。アプリケーションを設定するのを助けるためのインタラクティブガイドが表示されます。このガイドに従って基本的な使用法を理解することも、完了して終了 をクリックしてスキップすることもできます。
アクセストークンを要求
マシン間アプリケーションは安全であると仮定されるため(例: プライベートネットワークに配備されている)、OAuth 2.0 の "client_credentials" グラントを使用してアクセストークンを取得できます。クライアントを認証するために 基本認証 を使用します:
- リクエスト URL は Logto インスタンスのトークンエンドポイントです。マシン間アプリ詳細ページの 高度な設定 タブで見つけてコピーすることができます。
- リクエストメソッドは
POST
です。 - リクエスト
Content-Type
ヘッダーはapplication/x-www-form-urlencoded
です。 Authorization
ヘッダーの値はBasic <base64(app_id:app_secret)>
であり、app_id
とapp_secret
はマシン間アプリのアプリ ID とアプリシークレットです。アプリケーション詳細ページでそれらを見つけることができます。- リクエストボディにはグラントタイプと API 識別子を指定する必要があります。たとえば、
grant_type=client_credentials&resource=https://payment.example.com/api
です。grant_type=client_credentials
: "client_credentials" グラントの定数値。resource=https://payment.example.com/api
: クライアントがアクセスしたい API リソースの API 識別子。- アプリケーションにスコープ(権限)で承認する必要がある場合は、リクエストボディでスコープを指定することもできます。たとえば、
scope=read:payment write:payment
です。スコープについては後で説明します。
curl
を使用したリクエストの例は以下のとおりです:
成功した応答ボディは次のようになります:
承認ヘッダーを付けてリクエストを送信
今、アクセストークンを持っているので、API リソースへのリクエストの Authorization
ヘッダーにそれを追加することができます。たとえば、決済サービスの POST /payments
API を呼び出したい場合、次のリクエストを送信できます:
JWT を検証
決済サービスは JWK セットを使用して JWT を検証する必要があり、Logto から JWK セットを毎回取得するのを避けるためにローカル JWK セットキャッシュを持つ場合があります。幸いなことに、JWT の人気により、数行のコードで目標を達成するのを助ける多くのライブラリがあります。
これらのライブラリは通常、"jose" (JavaScript オブジェクト署名と暗号化) または "jsonwebtoken" と呼ばれます。たとえば、Node.js で jose を使用して JWT を検証できます:
検証が成功すると、payload
変数はデコードされた JWT ペイロードになります。それ以外の場合はエラーがスローされます。
ロールベースのアクセス制御を適用
これでカートサービスと決済サービス間の通信は安全にされました。しかし、認証フローはクライアントが本物のカートサービスであることを保証するだけで、カートサービスが決済サービス上でアクションを実行する許可を持っていることを保証しません。
たとえば、カートサービスが決済を作成することは許可されているが、決済を読むことは許可されていないとします。
パーミッションを定義
Logto では、"スコープ" と "パーミッション" は同義です。決済サービスの API リソースの詳細ページに移動し、パーミッション タブに移動します。現在は空になっているはずです。パーミッションを作成 をクリックし、パーミッション名 read:payment
を入力し、パーミッションの説明 決済を読む
を入力します。その後、パーミッションを作成 をクリックします。
上記の手順を繰り返して、パーミッション名 write:payment
と説明 決済を作成
を持つ別のパーミッションを作成します。
マシン間ロールを作成
ロールはパーミッションのグループです。Logto では、マシン間アプリにロールを割り当ててパーミッションを付与することができます。左のサイドバーで「ロール」をクリックし、ロールを作成 をクリックします。
- ロール名を
checkout
にし、ロール説明をチェックアウトサービス
にします。 - さらに表示 をクリックします。ロールタイプとして「マシン間アプリロール」を選択します。
- 「割り当てられたパーミッション」セクションで、API リソース名(決済サービス)の左にある矢印アイコンをクリックし、
write:payment
パーミッションを選択します。 - ロールを作成 をクリックします。
- 既にマシン間アプリ(カートサービス)があるので、次のステップで直接そのロールを割り当てることができます。アプリ名(カートサービス)の左にあるチェックボックスをチェックし、アプリケーションを割り当てる をクリックします。
スコープ付きのアクセストークンを要求
アクセストークンを要求 で説明したリクエストボディパラメータに加えて、リクエストボディでスコープを指定することもできます。たとえば、write:payment
パーミッションを要求したい場合は、次のリクエストを送信できます:
複数のスコープを要求するには、スペースで区切ることができます。たとえば、scope=write:payment read:payment
です。
スコープを検証
決済サービスで write:payment
パーミッションが必要なアクションの場合、JWT ペイロードの scope
クレームを確認することでスコープを検証できます:
結論
カートサービスへのアクセスを保護したい場合、同じ認証フローを適用することもできます。この場合、カートサービスが API リソースになり、クライアントはアクセスする必要のある別のサービスです。
Logto を使用すると、API リソースは OAuth 2.0 と JWT で保護され、ロールベースのアクセス制御を適用することで最小特権の原則に従うことができます。また、Logto を使用してユーザーとその権限を管理したり、サードパーティの ID プロバイダーと統合することもできます。