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 パラメータを指定した場合)、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 が、アクセス トークンに含まれているためです。<Scope> 要素が「B」に設定されているとしたら、当然、この呼び出しは失敗します。

まとめ

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

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