Azure API Managementを利用して、Client Credentials Flowを実装してみた

Client Credentials Flowとは

Client Credentials FlowとはOAuth2.0の認証フローの一つです。詳しくは【連載】世界一わかりみの深いOAuth入門 〜 その1:OAuthってなに? 〜 を参照してください。簡単に説明すると以下の図のように認証を行います。

今回はこの図の認可サーバーにMicrosoft Entra IDを用いて、リソースサーバーにAzure API Managementを用いた場合の構成を実装したいと思います。

Azure API ManagementとMicrosoft Entra IDの設定方法

Azure API ManagementとMicrosoft Entra IDを用いたClient Credentials Flowを実装する方法をこれから解説します。最初に認可サーバーであるMicrosoft Entra IDでクライアントIDとクライアントシークレットの設定を行います。次にリソースサーバーであるAzure API Manegementに認可サーバーと接続するための設定を行い、テスト用のAPIを作成します。

※この後行う実装からテストまでの工程は料金が発生する可能性があるので注意してください。

Microsoft Entra IDの設定

アプリの登録

まず最初にAPIMでホストするAPIをアプリケーションとしてEntra IDに登録します。

Microsoft Entra IDはすべてのサービスのIDカテゴリのID管理にあります。

Microsoft Entra ID > 管理 > アプリの登録から新規登録を押すことでアプリの登録ができます。今回は名前をoauthtestとしました。リダイレクトURIは不要です。

登録後、次のような画面に遷移します。

 

アプリケーションのURIの追加

Azure API ManagementからのAPIに対して認可を行うための設定を行います。先ほど遷移した画面からアプリケーションID URIの追加を押します。

APIの公開のページにあるアプリケーションID の URIと書かれている右にある追加を押します。するとアプリケーションID URIの編集がでるので、そのまま保存を押します。

 

また、今回はスコープも設定したいと思います。APIの公開のページからScopeの追加を押します。スコープ名を適当に設定し、同意できるのは管理者のみ、状態を有効とします。

クライアントシークレットの作成

※メモの準備をしてください。

 

クライアントシークレットの作成は登録したアプリの管理 > 証明書とシークレットから行います。新しいシークレットを押し、クライアントシークレットの追加を行います。有効期限は推奨の180日とします。追加を押すとクライアントシークレットが追加されます。

このときに表示されているクライアントシークレットの値は別のページに移動すると見れなくなるので追加した直後にすぐにメモしてください。

APIのアクセス許可

Entra IDに登録したアプリのアクセス許可の設定を行います。登録したアプリの管理 > APIのアクセス許可に移動してください。アクセス許可の追加をクリックするとアクセス許可の要求が出てきます。登録したアプリ(今回はoauthtest)を選択します。スコープも設定したので、チェックを付けてアクセス許可の追加を押します。

エンドポイントの確認

以上の操作を行ったら、登録したアプリの概要に移動してください。この後使用するのでページに載っているアプリケーション (クライアント) IDとディレクトリ (テナント) IDとアプリケーション ID の URIをメモしてください。次にエンドポイントを押してください。すると認証に必要なURLが表示されるので、この後使用するOAuth 2.0 承認エンドポイント (v2)、OAuth 2.0 トークン エンドポイント (v2)、OpenID Connect メタデータ ドキュメントの3つをメモしてください。これでEntra IDの設定は完了です。

Azure API Managementの設定

APIMのインスタンス作成

Entra IDの設定が終わったら、Azure API Managementのインスタンスを作成します。

Azure API ManagementはすべてのサービスのWeb & MobileのAPI管理にあります。

API Management サービスの作成を押すことで新規作成ができます。

今回のリソース名はAPIMoauthtestとしました。価格レベルはConsumptionがおすすめです。従量課金制ですが毎月100万回まで無料です。(Azure API Manegementの価格:https://azure.microsoft.com/ja-jp/pricing/details/api-management/#pricing

その他は変更せずインスタンスを作成しましょう。

APIMでOAuth 2.0認可サーバーの構成

OAuth2.0認可サーバーをAPIMに追加します。作成したAPIMのインスタンスに移動し、APIs > OAuth 2.0 + OpenID Connectに移動します。OAuth2.0のタブで追加を押して、以下の項目を入力します。

設定項目
表示名 何でもよい
ID 自動入力される
クライアント登録ページのURL 何でもよい(必須とは書かれていないが書かないとエラーが起きる)
クライアントの資格情報 チェック
承認エンドポイントのURL OAuth 2.0 承認エンドポイント (v2)
承認要求方法 2項目選択
トークンエンドポイントのURL OAuth 2.0 トークン エンドポイント (v2)
既定のスコープ {アプリケーション ID の URI}/.default
クライアントID アプリケーション (クライアント) ID
クライアントシークレット クライアントシークレットの値

テスト用のAPIを作成する

リソースを獲得するためのテスト用のAPIを作成します。APIs > APIに移動し、Add APIからHTTPを選択して、Display nameとNameを入力してCreateを押します。

その後、Add operationsを選択してDisplay nameとNameとURLを入力して、Responseのタブで200 OKを選択します。

作成したoperationのBackendの項目でエンドポイントの設定を行います。TargetをHTTP(s) endpointとし、ServiceURLをOverrideしてhttps://httpbin.org/anythingとします。Gateway credentialsはNoneとして保存します。ちなみにhttps://httpbin.orgとはHTTPのテストを行う際に、このURLに送ることでシンプルなHTTPリクエストとレスポンスを返してくれるので正しくAPIのリクエストが送られているか確認することができます。例えばhttps://httpbin.org/anythingにリクエスト送るとリクエストの内容を全て返却します。

サブスクリプションの設定

APIのサブスクリプションの設定を行います。APIs > サブスクリプションに移動し、サブスクリプションの追加を押します。名前を入力し、スコープをAPI、APIを先ほど作成したAPIの名前を入力します。作成したら、作成したサブスクリプションの右の方にある「・・・」からキーの表示を行って、主キーの値をメモしてください。

APIにOAuth 2.0認可を設定する

テスト用に作成したAPIのページを開いて、Settingのタブに移動します。下の方にSecurityという項目があるのでそこでOAuth2.0を選択し、OAuth 2.0 serverを先ほど作成した認可サーバーを設定します。

validate-jwt ポリシーをAPIに追加する

Designタブに移動し、All operationsを選択して、Inbound processingのAdd policyを押します。いくつか種類が出てくるのでValidate JWTを選択します。設定項目にFullがあるので選択して以下の項目を入力します。

設定項目
Validate by Header
Header name Authorization
Failed validation error message 認証が失敗しました
Issuers https://sts.windows.net/{ディレクトリ (テナント) ID}
Required claims > Name aud
Required claims > Match Any claim
Required claims > Values アプリケーション ID の URIとアプリケーション (クライアント) ID
Open ID URLs OpenID Connect メタデータ ドキュメント

以上の設定を保存すると以下のようなXML形式で確認できます。

<!--
    - Policies are applied in the order they appear.
    - Position <base/> inside a section to inherit policies from the outer scope.
    - Comments within policies are not preserved.
-->
<!-- Add policies as children to the <inbound>, <outbound>, <backend>, and <on-error> elements -->
<policies>
    <!-- Throttle, authorize, validate, cache, or transform the requests -->
    <inbound>
        <base />
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="認証が失敗しました" require-expiration-time="false" require-signed-tokens="false">
            <openid-config url="[OpenID Connect メタデータ ドキュメント]" />
            <issuers>
                <issuer>https://sts.windows.net/{ディレクトリ (テナント) ID}</issuer>
            </issuers>
            <required-claims>
                <claim name="aud" match="any">
                    <value>[アプリケーション ID の URI]</value>
                    <value>[アプリケーション (クライアント) ID]</value>
                </claim>
            </required-claims>
        </validate-jwt>
    </inbound>
    <!-- Control if and how the requests are forwarded to services  -->
    <backend>
        <base />
    </backend>
    <!-- Customize the responses -->
    <outbound>
        <base />
    </outbound>
    <!-- Handle exceptions and customize error responses  -->
    <on-error>
        <base />
    </on-error>
</policies>

これでAzure API Managementの設定は完了です。

リクエスト確認

今までの設定でClient Credentials Flowの検証を行う設定は整いました。それでは実際に下の図の工程をシェルスクリプトでテストしてみたいと思います。URLやIDはメモした値を適宜入れてください。

実行環境:

WSL2 – Ubuntu 22.04.3 LTS

bash : version : 5.1.16(1)-release (x86_64-pc-linux-gnu)

curl : version : 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/3.0.2 zlib/1.2.11 brotli/1.0.9 zstd/1.4.8 libidn2/2.3.2 libpsl/0.21.0 (+libidn2/2.3.2) libssh/0.9.6/openssl/zlib nghttp2/1.43.0 librtmp/2.3 OpenLDAP/2.5.16

jq : version : jq-1.6

#!/bin/bash


# トークンエンドポイントのURL
TOKEN_ENDPOINT="[OAuth 2.0 トークン エンドポイント (v2)]"


# クライアントIDとクライアントシークレット
CLIENT_ID="[アプリケーション (クライアント) ID]"
CLIENT_SECRET="[クライアントシークレット]"


# スコープ
SCOPE=" {アプリケーション ID の URI}/.default"


# APIのリクエストURL
REQUEST_URL="https://apimoauthtest.azure-api.net/test"


# azure subscription
SUBSCRIPTION_KEY="[サブスクリプションの主キー]"


# トークン取得用のcURLコマンドを実行(図の➀)
TOKEN_RESPONSE=$(curl -s -X POST $TOKEN_ENDPOINT \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=$SCOPE")
##  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET")


# レスポンスからアクセストークンを抽出
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')


# アクセストークンを表示
#echo "Access Token: $ACCESS_TOKEN"

# APIへリクエスト(図の③)
curl $REQUEST_URL -H "Ocp-Apim-Subscription-Key:$SUBSCRIPTION_KEY" -H "Authorization: Bearer $ACCESS_TOKEN"

このスクリプトでは図の➀と③を実行しています。

スクリプト内でトークン取得用のcurlコマンドを実行している部分は図の➀にあたる工程で、クライアントIDとクライアントシークレット(とスコープ)をリクエストに含め、Entra IDのトークンエンドポイントにリクエストを送っています。そのレスポンスからアクセストークンを取得することができます。アクセストークンを表示している部分のコメントアウトを外してみると実際のアクセストークンを見ることができます。

APIへリクエストを送っている部分は図の③にあたる工程で、アクセストークンをリクエストに含め、APIのリクエストURLにリクエストを送っています。

このスクリプトが実行するときに認証が正常に通れば、https://httpbin.org/anythingにリクエストが送られ、そのリクエストの内容全て返却されるので以下の結果が出力されます。

{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Authorization": "Bearer [アクセストークン]",
    "Client-Ip": "[IPアドレス]",
    "Disguised-Host": "apimoauthtest.azure-api.net",
    "Host": "httpbin.org",
    "Max-Forwards": "9",
    "Ocp-Apim-Subscription-Key": "[サブスクリプションキー]",
    "User-Agent": "curl/7.81.0",
    "Was-Default-Hostname": "apimwebappufvghnbkilctmh9yz92et3nmiuiguwlnabrr0toc.azurewebsites.net",
    "X-Amzn-Trace-Id": "Root=1-66f50cd8-4d773ed14da35b5e286b886f",
    "X-Appservice-Proto": "https",
    "X-Arr-Log-Id": "7e2ffcd8-b456-4d4b-88c5-bce1cf4bae66",
    "X-Arr-Ssl": "2048|256|CN=Microsoft Azure RSA TLS Issuing CA 03,O=Microsoft Corporation,C=US|CN=*.azure-api.net,O=Microsoft Corporation,L=Redmond,S=WA,C=US",
    "X-Forwarded-Tlsversion": "1.3",
    "X-Original-Url": "/test",
    "X-Site-Deployment-Id": "apimwebappufvghNBkiLcTmh9Yz92ET3NmiuIGUwlnAbRr0TOc",
    "X-Waws-Unencoded-Url": "/test"
  },
  "json": null,
  "method": "GET",
  "origin": "[IPアドレス]",
  "url": "https://httpbin.org/anything/test"
}

 

例えばシェルスクリプトのクライアントIDやクライアントシークレットをわざと違う値に変更して実行すると以下の結果が出力されます。

 

{ "statusCode": 401, "message": "認証が失敗しました" }

 

クライアントIDやクライアントシークレットが間違っていると認可サーバーからアクセストークンが渡されないのでリソースサーバーにアクセスすることができません。このようにしてClient Credentials Flowは認証を行うことができます。

 

まとめ

Azure API ManagementとMicrosoft Entra IDでOAuth2.0の認可フローの一つであるClient Credentials Flowを実装してみました。

注意することとして、今回行ったClient Credentials Flowはクライアントに対して認証を行っているだけでユーザー個人に対しては認証を行っていません。個人レベルの高度なセキュリティの認証を行いたい場合はAuthorization Code Flowなどを使いましょう。

 

参考文献

https://azure.microsoft.com/ja-jp/products/api-management

https://www.microsoft.com/ja-jp/security/business/identity-access/microsoft-entra-id

https://logico-jp.io/2019/10/01/protecting-apis-with-oauth2-client-credentials-grant-in-azure-api-management/

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です