OAuth2 スコープの操作

このページの内容は ApigeeApigee ハイブリッドに該当します。

Apigee Edge のドキュメントを表示する

このトピックでは、Apigee で OAuth 2.0 スコープを使用および適用する方法について説明します。

OAuth2 スコープとは

OAuth 2.0 スコープで、アクセス トークンに付与されるアクセス権の範囲を制限します。たとえば、クライアント アプリに発行するアクセス トークンに保護されたリソースの読み取り / 書き込みアクセス権を付与するか、読み取りアクセス権だけを付与するかを制御できます。API を実装して、必要に応じてスコープまたはスコープの組み合わせを適用できます。つまり、クライアントが読み取りスコープのトークンを受信して、書き込みアクセス権が必要な API エンドポイントを呼び出そうとすると、呼び出しは失敗します。

このトピックでは、アクセス トークンにスコープを割り当てる方法と、Apigee で OAuth 2.0 スコープを適用する方法について説明します。このトピックを読むと、自信を持ってスコープを使用できるようになります。

アクセス トークンにスコープを割り当てる方法

Apigee はアクセス トークンを生成する際に、そのトークンにスコープを割り当てることができます。割り当ての仕組みを理解するには、API プロダクト、デベロッパー、デベロッパー アプリという Apigee エンティティを理解する必要があります。概要については、公開の概要をご覧ください。先に進む前に、必要に応じてこの資料を確認することをおすすめします。

アクセス トークンはランダムな文字からなる長い文字列です。これによって Apigee は受信 API リクエストを確認できます(一般的なユーザー名とパスワードのクレデンシャルの代わりになると考えることができます)。厳密には、トークンは次のようなメタデータのコレクションを参照するキーです。

{
  "issued_at" : "1416962591727",
  "application_name" : "0d3e1d41-a59f-4d74-957e-d4e3275d4781",
  "scope" : "A",
  "status" : "approved",
  "api_product_list" : "[scopecheck1-bs0cSuqS9y]",
  "expires_in" : "1799", //--in seconds
  "developer.email" : "scopecheck1-AdBmANhsag@apigee.com",
  "organization_id" : "0",
  "token_type" : "BearerToken",
  "client_id" : "eTtB7w5lvk3DnOZNGReBlvGvIAeAywun",
  "access_token" : "ODm47ris5AlEty8TDc1itwYPe5MW",
  "organization_name" : "wwitman",
  "refresh_token_expires_in" : "0", //--in seconds
  "refresh_count" : "0"
}

トークンのメタデータには実際のアクセス トークン文字列と有効期限情報、そのトークンに関連付けられたデベロッパー アプリ、デベロッパー、プロダクトを識別するものが含まれます。また、メタデータには「スコープ」も含まれます。

トークンにスコープを割り当てる仕組み

スコープを理解するには、まず、デベロッパー アプリ内の各プロダクトにゼロまたは複数のスコープが割り当てられることを覚えることが大事です。これらのスコープは、プロダクトの作成時に割り当てることも後から追加することもできます。スコープは名前のリストとして存在し、各プロダクトに関連付けられる「メタデータ」に含まれています。

デベロッパー アプリを作成してプロダクトを追加すると、Apigee はデベロッパー アプリ内のすべてのプロダクトを調べ、それらのプロダクトの全スコープのリスト(アプリのマスターリストまたはグローバル スコープリスト - 認識されたすべてのスコープをまとめたもの)を作成します。

クライアント アプリが Apigee にアクセス トークンをリスエストする際は、トークンに関連付けるスコープも任意で指定できます。たとえば、以下のリクエストではスコープ「A」を関連付けるように要求しています。つまり、クライアントは認可サーバー(Apigee)に対してスコープ「A」を割り当てたアクセス トークンを生成するよう要求しています(これにより、スコープ「A」が割り当てられた API を呼び出す権限がアプリに付与されます)。この場合、アプリは次のような POST リクエストを送信します。

curl -i -X POST -H Authorization: Basic Mg12YTk2UkEIyIBCrtro1QpIG \
  -H content-type:application/x-www-form-urlencoded \
  https://apitest.acme.com/oauth/token?grant_type=client_credentials&scope=A

挙動

Apigee はこのリクエストを受信すると、リクエストを作成したアプリや、クライアントが登録したデベロッパー アプリを把握します(クライアント ID とクライアント シークレットという 2 つのキーが基本認証ヘッダーにエンコードされています)。scope クエリ パラメータが含まれているため、Apigee はデベロッパー アプリに関連付けられた API プロダクトのいずれかにスコープ「A」が設定されているかどうかを判断する必要があります。該当するプロダクトがある場合は、スコープ「A」を割り当てたアクセス トークンが生成されます。別の観点から見ると、scope クエリ パラメータはある種のフィルタになります。たとえば、デベロッパー アプリがスコープ「A」、「B」、「X」を認識する場合、クエリ パラメータで「scope=X Y Z」が指定されていればスコープ「X」のみがトークンに割り当てられます。

クライアントが scope パラメータを指定していない場合、Apigee はデベロッパー アプリで認識されたすべてのスコープを含むトークンを生成します。デフォルトの動作では、デベロッパー アプリに含まれるすべてのプロダクトのすべてのスコープを含むアクセス トークンが返されるということを理解しておくことが重要です。

デベロッパー アプリに関連付けられているどのプロダクトにもスコープが指定されていないのにトークンにスコープが割り当てられている場合、そのトークンを使用した呼び出しは失敗します。

たとえば、デベロッパー アプリがスコープ A、B、C、D を認識するとします。これが、アプリのスコープのマスターリストです。アプリ内のあるプロダクトにスコープ A と B が割り当てられていて、別のプロダクトにスコープ C と D が割り当てられているといったように、任意の組み合わせでスコープが設定されています。クライアントが scope パラメータを指定していない場合(または値のない scope パラメータを指定している場合)、トークンには 4 つのスコープ(A、B、C、D)がすべて付与されます。この場合も、トークンはデベロッパー アプリによって認識されるすべてのスコープである一連のスコープを受け取ります。

デフォルトの動作によって、認識されているすべてのスコープが設定されたアクセス トークンが返されるというケースがもう 1 つあります。それは GenerateAccessToken ポリシー(アクセス トークンを生成する Apigee ポリシー)で <Scope> 要素が指定されていない場合です。たとえば、次の GenerateAccessToken ポリシーでは <Scope>指定されています。その <Scope> 要素がない場合(または存在するが空の場合)、デフォルトの動作が実行されます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-GenerateAccessToken">
    <DisplayName>OAuthV2 - Generate Access Token</DisplayName>
    <Attributes>
      <Attribute name='hello' ref='system.time' display='false'>value1</Attribute>
    </Attributes>
    <Scope>request.queryparam.scope</Scope> 
    <GrantType>request.formparam.grant_type</GrantType>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>GenerateAccessToken</Operation>
    <SupportedGrantTypes>
      <GrantType>client_credentials</GrantType>
    </SupportedGrantTypes>
  <GenerateResponse enabled="true"/>
</OAuthV2>

スコープが適用される仕組み

まず、Apigee ではアクセス トークンが OAuthV2 ポリシーで検証される点に留意してください(通常はプロキシフローの先頭に配置されます)。このポリシーでは VerifyAccessToken オペレーションが指定されている必要があります。このポリシーの内容を見てみましょう。

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenA">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A</Scope> <!-- Optional: space-separated list of scope names. -->
    <GenerateResponse enabled="true"/>
</OAuthV2>

<Scope> 要素に注目してください。この要素を使用して、ポリシーが受け入れるスコープを指定します。

この例では、アクセス トークンにスコープ「A」が含まれていればポリシーは成功します。この <Scope> 要素が省略されている場合や値がない場合、ポリシーはアクセス トークンのスコープを無視します。

スコープに基づいてアクセス トークンを検証する機能を使用し、特定のスコープを適用するように API を設計できます。そのためには、スコープ対応の VerifyAccessToken ポリシーをアタッチしてカスタムフローを作成します。

API にエンドポイント /resourceA のフローが定義されているとします。

<Flow name="resourceA">
            <Condition>(proxy.pathsuffix MatchesPath "/resourceA") and (request.verb = "GET")</Condition>
            <Description>Get a resource A</Description>
            <Request>
                <Step>
                    <Name>OAuthV2-VerifyAccessTokenA</Name>
                </Step>
            </Request>
            <Response>
                <Step>
                    <Name>AssignMessage-CreateResponse</Name>
                </Step>
            </Response>
        </Flow>

このフローがトリガーされると(リクエストのパス サフィックスに /resourceA が含まれる場合)、OAuthV2-VerifyAccessTokenA ポリシーがすぐに呼び出されます。このポリシーは、アクセス トークンが有効であることを確認すると、トークンがサポートしているスコープを調べます。次のように <Scope>A</Scope> を設定してポリシーが構成されている場合、アクセス トークンにスコープ「A」が割り当てられている場合にのみポリシーが成功します。それ以外の場合はエラーが返されます。

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenA">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A</Scope>
    <GenerateResponse enabled="true"/>
</OAuthV2>

まとめると、API デベロッパーは API にスコープが適用されるように設計する必要があります。そのためには、デベロッパーが特定のスコープを処理するカスタムフローを作成し、スコープを適用する VerifyAccessToken ポリシーをアタッチする必要があります。

コードの例

最後に、トークンにスコープが割り当てられて、スコープが適用される仕組みを説明する API 呼び出しの例を見ていきましょう。

デフォルトの場合

デベロッパー アプリに複数のプロダクトが含まれていて、それらのプロダクトのスコープが A、B、C だとします。この API 呼び出しはアクセス トークンをリクエストしますが、scope クエリ パラメータは指定されていません。

curl -X POST -H content-type:application/x-www-form-urlencoded \
  https://apitest.acme.com/scopecheck1/token?grant_type=client_credentials

この場合、生成されるトークンにはスコープ A、B、C が割り当てられます(デフォルトの動作)。このトークンのメタデータは次のような内容になります。

{
  "issued_at" : "1417016208588",
  "application_name" : "eb1a0333-5775-4116-9eb2-c36075ddc360",
  "scope" : "A B C",
  "status" : "approved",
  "api_product_list" : "[scopecheck1-yEgQbQqjRR]",
  "expires_in" : "1799", //--in seconds
  "developer.email" : "scopecheck1-yxiuHuZcDW@apigee.com",
  "organization_id" : "0",
  "token_type" : "BearerToken",
  "client_id" : "atGFvl3jgA0pJd05rXKHeNAC69naDmpW",
  "access_token" : "MveXpj4UYXol38thNoJYIa8fBGlI",
  "organization_name" : "wwitman",
  "refresh_token_expires_in" : "0", //--in seconds
  "refresh_count" : "0"
}

ここで、API エンドポイントにスコープ「A」が設定されているとします(つまり、VerifyAccessToken にスコープ「A」が含まれている必要があります)。VerifyAccessToken ポリシーの内容は次のとおりです。

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenA">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A</Scope>
    <GenerateResponse enabled="true"/>
</OAuthV2>

たとえば、スコープ A を適用するエンドポイントを次のように呼び出すとします。

curl -X GET -H Authorization: Bearer MveXpj4UYXol38thNoJYIa8fBGlI \
  https://apitest.acme.com/scopecheck1/resourceA

この GET 呼び出しは成功します。

 {
   "hello" : "Tue, 25 Nov 2014 01:35:53 UTC"
 }

エンドポイントが呼び出されるとトリガーされる VerifyAccessToken ポリシーにスコープ A が必要ですが、アクセス トークンにはデフォルトの動作でスコープ A、B、C が付与されているからです。

フィルタリングする場合

スコープが A、B、C、X が設定されたプロダクトを含むデベロッパー アプリがあるとします。アクセス トークンをリクエストし、次のように scope クエリ パラメータを含めます。

curl -i -X POST -H content-type:application/x-www-form-urlencoded \
  'https://apitest.acme.com/oauth/token?grant_type=client_credentials&scope=A X'

この場合、生成されるトークンにはスコープ A と X が割り当てられます。これは、A と X の両方が有効なスコープだからです。デベロッパー アプリはスコープ A、B、C、X を認識することを思い出してください。上記の例では、これらのスコープに基づいて API プロダクトのリストをフィルタリングしています。プロダクトにスコープ A または X が設定されている場合、これらのスコープを適用する API エンドポイントを構成できます。プロダクトにスコープ A も X も設定されていなければ(たとえば B、C、Z が設定されている場合)、スコープ A または X を適用する API をこのトークンで呼び出すことはできません。

次のように、新しいトークンを使用して API を呼び出すとします。

curl -X GET -H Authorization: Bearer Rkmqo2UkEIyIBCrtro1QpIG \
  https://apitest.acme.com/scopecheck1/resourceX

アクセス トークンが API プロキシで検証されます。次に例を示します。

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenX">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A X</Scope>
    <GenerateResponse enabled="true"/>
</OAuthV2>

GET 呼び出しトリガーが成功して、レスポンスを返します。次に例を示します。

 {
   "hello" : "Tue, 25 Nov 2014 01:35:53 UTC"
 }
 

VerifyAccessToken ポリシーにスコープ A または X が必要で、アクセス トークンにスコープ A と X が含まれているため、この呼び出しは成功します。もちろん、<Scope> 要素が「B」に設定されていた場合、この呼び出しは失敗します。

まとめ

Apigee が OAuth 2.0 スコープを処理する仕組みを理解しておくことは重要です。以下に重要な点をまとめます。

  • デベロッパー アプリは、すべてのプロダクトに定義されているすべてのスコープを「認識」します。
  • アプリでアクセス トークンをリクエストすると、適用するスコープを指定できます。(a)リクエストされたスコープと(b)デベロッパー アプリで認識されたスコープに基づいて、アクセス トークンに実際に割り当てるスコープを決定するのは Apigee(認可サーバー)です。
  • Apigee がスコープを確認する構成になっていない場合(<Scope> 要素が VerifyAccessToken ポリシーに含まれていない場合、または空の場合)、アクセス トークンに埋め込まれたスコープが登録されたデベロッパー アプリで認識されるスコープ(アプリのスコープ「マスター」リストにあるいずれかのスコープ)と一致すれば API 呼び出しは成功します。
  • アクセス トークンにスコープが 1 つも関連付けられていない場合、API 呼び出しが成功するのは Apigee がスコープを考慮しない場合(VerifyAccessToken ポリシーに <Scope> 要素がないか、値が空の場合)だけです。