こんにちは、サイオステクノロジー 技術部武井です。今回は、AzureのAPI Gateway(API Management)を用いてOpenID Connect Providerより発行されたJWTを検証してみます。
API Gatewayとは?
AzureのAPI Managementは、API Gatewayの製品です。プロダクトのWebサイトには以下のような説明がありました。
API Management を使用するには、管理者が API を作成します。 API はそれぞれ、少なくとも 1 つの操作で構成され、1 つまたは複数の成果物に追加することができます。 API を使用する開発者は、その API を含んだ成果物をサブスクライブしたうえで、使用ポリシーが適用されていればその範囲の中で、API の操作を呼び出すことができます。
ワケ(´・ω・`)ワカ(´ε`;)リマ( ;∀;)セン(ノД`)
多分こういうことだと思います。
APIに関わる横断的な処理を一手に引き受けてくれます。つまり、APIを開発する際、今までAPIの認証、ロギング、課金管理などはAPIごとに実装していました。しかし、そうではなく、全てのAPIへのアクセスは、一旦、API Gatewayなるものに全てアクセスさせて、そこで認証、ロギングなどを行い、それからAPIにその要求を引き渡すということをしてくれます。
API Gatewayがない場合は、こんな感じだったと思います。APIごとに認証やロギングを実装しています。
API Gatewayを使えば、こんなにハッピーになれます。認証やロギングをまず一手にAPI Gatewayで引き受けて、その処理が正常に完了したものだけ、バックエンドにあるAPIにその要求を引き渡します。
今回のゴール
今回は、このAPI Gatewayを使って、OpenID ConnectのProviderで発行されたID Token(JSON Web Token)を検証して、Open ID ConnectのProviderで認証を受けていないAPIは、その要求を拒否(HTTPステータスコード401を返す)ということをしたいのです。
つまり絵にすると、以下のようになります。
上図のとおりなのですが、今回はOpenID ConnectのプロバイダーとしてYahooを使います。Yahooが発行するID Tokenは、API Management(後述)で検証可能なJSON Web Tokenの仕様に準じているからです。
API GatewayにはAzureのAPI Managementを使います。OpenID Connectのプロバイダーが発行したID Token(JSON Web Token)の検証が行えます。
API Gatewayの背後に置くAPIについては、何でもよかったのですが、API ManagementからさくっとインポートできるAzure Functionsをチョイスしました。
実際にやってみましょう
では、早速いつもどおり実践してみます(๑•̀ㅂ•́)و✧
APIを作る
まずAPI Gatewayの背後に置くAPIを作ります。Azure Functionsでさっくり作ります。このあたりあまり今回の本質ではないので、説明はざっくりしてしまいます。もし、ご自分で公開されているAPIをお持ちであれば、そちらを使っていただいても構いません。
Azureポータルにログインして、「リソースの作成」をクリックして、「function」と入力してフィルターすると「Function App」が出てきますので、クリックします。
「作成」をクリックします。
「アプリ名」は任意の名称、「サブスリプション」「リソースグループ」「Storage」は環境に応じたものを、他は以下のように入力して、「作成」をクリックします。
しばらくすると、Azureポータルの上の通知アイコン(鈴みたいなやつ)に以下のように作成に成功した旨が表示されるので「リソースグループに移動」をクリックします(切れてますが)。
「関数」のとなりにある「+」をクリックします。
今回はRestAPIを作成しますので、「webhook + API」を選択して、言語は「JavaScript」を選択します。言語は何でもいいのですが、私がJavaな人間なので、JavaScriptにしました。最後に「この関数を作成する」をクリックします。
こんな感じでAPIが出来ます。Postでnameというパラメータに値を渡すと「Hello [名前]」というレスポンスが返ってくる単純なものです。実行してみましょう。「関数のURLの取得」をクリックしてください。
「コピー」をクリックします。
上記でコピーしたURLを元に以下のcurl文でリクエストを投げてみてください。
# curl -H 'Content-Type:application/json' -d "{\"name\":\"hoge\"}" https://apim-test-api.azurewebsites.net/api/HttpTriggerJS1?code=XXXXXX
“Hello hoge”と返ってくれば成功です。
とりあえずAPI(Azure Functions)のところは終わりました。次は、YahooでOpenID ConnectのProviderを作ります。
OpenID ConnectのProviderを作る
以下のURLにアクセスしてください。
https://e.developer.yahoo.co.jp/dashboard/
「新しいアプリケーションを開発」をクリックします。
「サーバーサイド」の方をチェックします。
「アプリケーション名」に任意のアプリケーション名を入力します。あとはデフォルトのままでOKです。
「同意する」をチェックして「確認」をクリックします。
入力内容に問題ないことを確認して「登録」をクリックします。
「Client ID」「シークレット」をメモっておいてください。後で使います。
OpenID ConnectのProvider構築まで完了しました。
API Gatewayを作る
Azureポータルにアクセスして、「すべてのサービス」をクリックして、下記のように「api」と入力すると、「API Managementサービス」が表示されますので、クリックします。
「追加」をクリックします。
「名前」「組織名」に任意の名称、「サブスリプション」「リソースグループ」「場所」は環境に応じた値を、「管理者のメールアドレス」はAPI Managementサービスの作成が完了したときに通知されるメールアドレスを、「価格レベル」は「開発者(いいえ SLA)」を選択して、「作成」をクリックします。しばらくするとAPI Managementのサービスが作成されます。
そして、これからこのAPI Gatewayの背後に配置するAPIを設定します。それが先程Azure Functionsで作成したものになるわけですが、その前にもう一つやることがあります。API ManagementにAPIを配置するまえにAPIの定義を作成しなければなりません。これはOpen APIという仕様で定義されているのですが、このAPIがどのようなMethod(Post or Get or …)やどのようなパラメータを取るかを定義したものです。この定義を作成するために一旦、先程のAzure Functionsの画面に戻ります。以下の画面の右下の方にある「APIの定義」をクリックしてください。
「関数(プレビュー)」をクリックします。
「API定義テンプレートを生成する」→「保存」の順にクリックします。これでAPIの定義が出来上がりました。
では、API Managementの画面に戻りましょう。以下の画面で「API」をクリックします。
API Managementの背後に、先程作成したAzure Functionsを配置したいので「Function App」をクリックします。
「Browse」をクリックすると、Azure Functionsを選択する画面が表示されますので、先程作成したAzure Functionsをクリックします。また「Products」には「Unlimited」を選択して、「Create」をクリックします。
デフォルトではAzure Functionsはキーで保護されています。つまり、Azure Functionsを呼び出す際、URLのクエリパラメーターのcodeに、Azure Functionsを作成した際に発行されたキーを指定しないとAzure Functionsが呼び出せないのです。先程、以下のcurlコマンドを打って、Azure Functionsを試したと思います。
# curl -H 'Content-Type:application/json' -d "{\"name\":\"hoge\"}" https://apim-test-api.azurewebsites.net/api/HttpTriggerJS1?code=XXXXXX
上記のcode=XXXXXXで指定されているXXXXXXの値をメモっておいてください。API ManagementからAPI、つまりAzure Functionsを呼び出すときにはこのcodeを渡さないといけないので、これからこのcodeの定義を行います。以下の画面の「名前付きの値」をクリックします。
リストに表示されているAzure Functionsをクリックします。
「値の表示」をクリックすると、テキストボックスが表示されますので、その中に先程のcodeの値を入力します。これでAPI ManagementからAPI(Azure Functions)を呼び出したときに
codeの値がクエリパラメーターとして渡されるようになります。
これからOpenID ConnectのProviderから発行されるID Token(JSON Web Token)を検証するためのポリシーを設定します。「API」→「api-test-api.azurewebsite…(先ほどインポートしたAzure Functions)」→「Post ・・・」の順にクリックします(ちなみにここにGETやらPUTやらいろんなメソッドのAPIのインターフェースが表示されていますが、これは先程作成したAPIの定義に基づくものです)。
「Inbound processing」の鉛筆マークをクリックします。
「</> Code View」をクリックします。
右のメニューから「Validate JWT」をクリックして、以下のようなXMLを作成します。
先程のXMLを整形して、<validate-jwt>~</validate-jwt>を以下のようなXMLにします。最後に「Save」をクリックします。
これで設定は全て終わりです。
試してみる
では、早速試してみたいと思います。OpenID ConnectのProviderで発行されるID Tokenを取得します。以下のURLをブラウザのURL入力欄に入力します。
https://auth.login.yahoo.co.jp/yconnect/v2/authorization?client_id=[Yahooの設定画面で取得したClient ID] &response_type=id_token&redirect_uri=https://developer.yahoo.co.jp/start/&scope=openid&state=hoge&nonce=hoge
※stateとnonceは適当です(´・ω・`)
response_typeはOAuthでいうところのフローを選択する項目なのですが、ここではid_tokenつまりImplicit Flowを選択してます。
以下のURLにリダイレクトされます。
https://developer.yahoo.co.jp/start/#state=hoge&id_token=[ID Token]
id_tokenのクエリパラメータで指定されている値がID Tokenになります。ペイロードの部分をBase64エンコードしてみます。
{ "iss":"https://auth.login.yahoo.co.jp/yconnec/v2", "sub":"LBHNLYCPWDV3UTO5HVVBFIXTMA", "aud":[Yahooの設定画面で取得したClient ID], "exp":1535714821, "iat":1533295621, "amr":["pwd"], "nonce":"hoge" }
おおっ。JSON Web Tokenっぽい(๑•̀ㅂ•́)و✧
そして次はAPI Managementで定義されたAPIを叩いてみましょう。まずその前にAPIのURLを取得します。
「API」→「api-test-api.azurewebsite…(先ほどインポートしたAzure Functions)」→「Settings」の順にクリックして表示される「Web Service URL」がAPIのURLになりますので、メモします。
次にそのまま「Test」のタブをクリックします。「Ocp-Apim-Subscription-Key」の値をメモします(目のマークをクリックすると表示されます)。
以下のcurlコマンドを実行します。
curl -D - -H 'Content-Type:application/json' -H 'Ocp-Apim-Subscription-Key:[先程メモしたOcp-Apim-Subscription-Keyの値]' -H 'Authorization:Bearer [先程メモしたID Token]' -d "{\"name\":\"hoge\"}" [先程メモしたAPIのURL]/api/HttpTriggerJS1
すると・・・
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Length: 12 Content-Type: application/json; charset=utf-8 Expires: -1 X-Powered-By: ASP.NET Date: Fri, 03 Aug 2018 11:35:42 GMT "Hello hoge"
おおヮ(゚д゚)ォ!
API Managementの背後に置いてあるAzure Functionsが実行されました(๑•̀ㅂ•́)و✧
最後に
今までは、ID Tokenの検証はアプリケーションの中で実装してたのですが、API Managementがあればそんな実装は不要ですね。Azureバンザイ٩(๑´3`๑)۶