OAuth2 スコープの使用

このトピックでは、Apigee Edge で OAuth 2.0 スコープを使用する方法を説明します。

OAuth2 スコープとは

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

このトピックでは、アクセス トークンにスコープが割り当てられる仕組みと、Apigee Edge が OAuth 2.0 スコープを適用する仕組みについて説明します。このトピックを読み終えると、確実にスコープを使用できるようになります。

アクセス トークンにスコープが割り当てられる仕組み

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

アクセス トークンは、Edge に受信 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"
}

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

トークンのスコープはどのようにして割り当てられるのでしょうか?

スコープを理解する最初の鍵として、デベロッパー アプリに含まれる各プロダクトには、ゼロまたは複数のスコープを割り当てられることを覚えておいてください。プロダクトのスコープは、そのプロダクトの作成時に割り当てることも、後から追加することもできます。スコープは名前のリストとして、各プロダクトに関連付けられた「メタデータ」に組み込まれます。

デベロッパー アプリを作成して、そのアプリにプロダクトを追加すると、Edge はデベロッパー アプリに含まれるすべてのプロダクトを調べ、それらのプロダクトのすべてのスコープを含めたリストを作成します(アプリのマスターリストまたはグローバル スコープリストで認識されたすべてのスコープの和集合)。

クライアント アプリが Apigee Edge にアクセス トークンをリスエストする際は、オプションで、トークンに関連付けるスコープもリクエストできます。次の例では、スコープ「A」を関連付けるようリクエストしています。つまり、クライアントは認証サーバー(Edge)に対し、スコープ「A」を関連付けたアクセス トークンを生成するようリクエストしています(このアクセス トークンにより、スコープ「A」が設定された API の呼び出しがクライアント アプリに認証されます)。この場合、アプリは次のような POST リクエストを送信します。

curl -i -X POST -H Authorization: Basic Mg12YTk2UkEIyIBCrtro1QpIG -H content-type:application/x-www-form-urlencoded http://myorg-test.apigee.net/oauth/token?grant_type=client_credentials&scope=A

どのような結果になるでしょうか?

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

クライアントが scope パラメータを指定しないとどうなるでしょうか?この場合、Edge は、デベロッパー アプリによって認識されるすべてのスコープを含むトークンを生成します。デフォルトの動作では、デベロッパー アプリに含まれるすべてのプロダクトのすべてのスコープの和集合を含むアクセス トークンが返されます。

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

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

デフォルトの動作で認識されているすべてのスコープが割り当てられたトークンが返される場合はもう 1 つあります。それは、GenerateAccessToken ポリシー(アクセス トークンを生成する Apigee Edge ポリシー)で <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 Edge ではアクセス トークンを検証するために 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 http://wwitman-test.apigee.net/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」が設定されているとします(つまり、この API エンドポイントの 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 http://wwitman-test.apigee.net/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 'http://myorg-test.apigee.net/oauth/token?grant_type=client_credentials&scope=A X'

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

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

curl -X GET -H Authorization: Bearer Rkmqo2UkEIyIBCrtro1QpIG http://wwitman-test.apigee.net/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 Edge が OAuth 2.0 スコープを処理する仕組みを理解することは重要です。以下に、覚えておくべき重要な点をまとめます。

  • デベロッパー アプリは、そのアプリに含まれるすべてのプロダクトに定義されているすべてのスコープの和集合を「認識」します。
  • アプリがアクセス トークンをリクエストするときは、トークンに割り当てるスコープもリクエストできます。アクセス トークンに実際に割り当てるスコープを決定するのは、Apigee Edge(認証サーバー)の役目です。Apigee Edge はこの決定を、(a)リクエストされたスコープと、(b)デベロッパー アプリが認識するスコープに基づいて行います。
  • Apigee Edge がスコープを確認するように構成されていない場合(VerifyAccessToken ポリシーに <Scope> 要素がないか、空の場合)は、アクセス トークンに埋め込まれたスコープが登録済みのデベロッパー アプリにより認識されたスコープの 1 つ(アプリのスコープの「マスター」リストに含まれるスコープの 1 つ)に一致する限り、API 呼び出しは成功します。
  • アクセス トークンにスコープが 1 つも関連付けられていない場合、API 呼び出しが成功するのは、Edge がスコープを考慮しない場合(VerifyAccessToken ポリシーで <Scope> 要素が欠落しているか、要素が空の場合)のみです。