はじめてのOkta Workforce Identity Cloud (WIC) [第8回] APIってどうやって使うの? (OAuth 2.0 Client Credentials編)
目次
- Client Credentialsグラントについて
- Client Credentialsグラントの設定
- JWTの生成
- トークンリクエストの発行とアクセストークンの取得
- APIリクエストの発行
- まとめ
本ブログ記事では、前回に続き、Okta Workforce Identity Cloud (以降、Okta WIC) でのOAuth 2.0を使ったAPIの使い方について解説します。
前回の「認可コードグラント」は、ユーザーの認証が必要 (=人の介在が必要) なので、「サーバからOkta WICへAPIでアクセスしたい」という場合には使いづらい方式でした。
本ブログ記事では、OAuth 2.0の中でも「Client Credentials グラント」という、ユーザーの認証を必要としない方式について解説します。
OAuth 2.0では「サーバ」という言葉を用いると混乱が生じる気がするので、以降は「マシン」と呼ぶことにします。
Client Credentials グラントでは、認可コードグラントで必要だった「人による認証」のステップが不要になることとの引き換えに、クライアント (マシン) と認可サーバとの間での「事前の情報共有」にて認証を行う必要があります。
Client Credentials グラントの仕様では、その「事前の情報共有」の方式として、認可サーバで生成した「クライアントID」と「クライアントシークレット」をクライアント (マシン) にあらかじめ設定しておき、クライアントはそれらを認可サーバに送って認証する、という方式も規定されています。
しかし、Okta WICのOAuth 2.0 APIでは、セキュリティ的な観点から「秘密鍵/公開鍵を使ったJSON Web Token (JWT)」を使う方式だけがサポートされており、「クライアント ID」 と「クライアントシークレット」の方式を使うことはできません。
「秘密鍵/公開鍵を使ったJSON Web Token (JWT)」も「事前の情報共有」方式ですが、認可サーバはあらかじめ公開鍵を保持し、クライアント (マシン) は、自身が保持する秘密鍵で署名した情報を認可サーバに送り (=秘密鍵は送らない)、認可サーバは公開鍵を使ってその署名を検証することで認証する、という方式です。
(ちなみにJWTは「ジョット」と読みます。)
以降の解説は、以下のブログ記事の内容を実施済み (理解済み) の前提で進めていきます。
・はじめてのOkta Workforce Identity Cloud (WIC) トライアル環境の構築
・はじめてのOkta Workforce Identity Cloud (WIC) [第1回] ユーザーと認証器の関係を紐解く
・はじめてのOkta Workforce Identity Cloud (WIC) [第2回] 多要素認証を紐解く
・はじめてのOkta Workforce Identity Cloud (WIC) [第6回] APIってどうやって使うの? (トークン編)
・はじめてのOkta Workforce Identity Cloud (WIC) [第7回] APIってどうやって使うの? (OAuth 2.0 認可コード編)
Client Credentialsグラントについて
設定に入る前に、まずはOAuth 2.0の用語解説と、Client Credentials グラントがどの様な動きをするのかを解説します。
OAuth 2.0の4つの役割
OAuth 2.0には、以下 A, B, C, D の4つの役割が存在します。
A.「リソースオーナー」= リソースの所有者です。Client Credentialsの場合は「マシン」が該当します。
B.「クライアント」= APIを使って、リソースサーバ (Okta WIC) 上のリソースへアクセスするアプリケーションです。Client Credentialsの場合は、これも「マシン」が該当します。
C.「認可サーバ」= クライアント (マシン) の検証を行って、問題なければ「アクセストークン」を発行するサーバです。(=Okta WICです。)
Client Credentialsの場合は「認可エンドポイント」は登場せず、「トークンエンドポイント」だけが登場します。
D.「リソースサーバ」= クライアント (マシン) からの、アクセストークンを持ったAPIリクエストを受け取って、それに問題がなければ、要求されたリソースを返すサーバです。(=Okta WICです。)
===== << Tips >> =====
前回に続き今回においても「Okta WICのAPI」に言及している関係で、「C.認可サーバ」も「D.リソースサーバ」もOkta WICに同居している記載にしていますが、それらは全く異なる役割を担っているので、頭の中では「それぞれ別々に存在している」とお考え頂く方が、OAuthへの理解は深まると思います。
実際、認可サーバはOkta WICを使うけれども、リソースサーバはA社やB社を使う、というケースも多いです。(その場合、Okta WICでは別途 API Access Management ライセンスが必要です。)
前回と今回で紹介している「C.認可サーバ」と「D.リソースサーバ」が同居するケースは、Okta WICのような「認証基盤サービス自体にAPIアクセスしたい」という要件の場合なので、OAuth全体からすると、むしろ少数派かもしれません。
===================
Client Credentialsグラントの流れ
上記の役割を踏まえ、以下の図を用いてOAuth 2.0 Client Credentialsグラントの流れを説明します。
◾️ 下図の流れは、Okta WICおよびマシンに必要な設定は完了している前提です。
◾️ 下図の変数名は、日本語で表現しています。(厳密さは欠きますが、分かりやすさを優先して。)
(1) クライアント (マシン) からは、以下の値を持ったトークンリクエストが、認可サーバのトークンエンドポイント (https://xxxxx.okta.com/oauth2/v1/token) に向けて発行されます。
◾️ 「グラントタイプ = client_credentials」: トークンエンドポイントに対して、「Client Credentialsグラントの方式でアクセストークンを発行してください。」と伝えています。
◾️ 「スコープ = okta.users.read okta.groups.read」: 『「ユーザ情報」と「グループ情報」を取得したいです!』という宣言です。
◾️ 「クライアントアサーションタイプ = urn:ietf:params:oauth:client-assertion-type:jwt-bearer」: 以下の「クライアントアサーションはどのようなタイプか?」を説明しています。末尾から「JWT Bearer (ベアラー)」であることが分かります。
===== << Tips >> =====
Bearer (ベアラー ) とは、電車でいうところの切符のような位置付けで、「切符 (JWT) が正当なものかどうかは確認するけど、誰が持ってきたのかの身元までは確認しない。」というものです。
===================
◾️ 「クライアントアサーション = JWT」: 「ヘッダ」、「ペイロード」、「署名」の3つの情報を合わせ持つ「JWT」というデータを送ります。
(その作成方法は、後の「JWTの生成」の章で説明します。)
(2) トークンエンドポイントは、公開鍵 (クライアントが所持している秘密鍵とペアになっている鍵で、認可サーバに事前に共有しているもの) を用いて、JWTの署名を検証します。
ここでは、署名の検証が成功したと仮定します。
(3) トークンエンドポイントは、アクセストークンを生成します。
(4) トークンエンドポイントは、「アクセストークン」を含むトークンレスポンスをクライアント (マシン) へ送ります。
この (4) までのステップで、クライアント (マシン) は「アクセストークン」を受け取ることができたので、一区切りです。
以降は、クライアント (マシン) から「アクセストークンを含んだAPIリクエスト」をリソースサーバへ送ることで、Okta WICからユーザーやグループの情報を得ることが可能になります。
(クライアントと認可サーバのスコープ値次第で、APIを使った設定 (ユーザーやグループの追加や削除等) も可能になります。)
以下、一例です。
(5) クライアント (マシン) は、リソースサーバに対して、グループ情報を得るためのURL (https://xxxxx.okta.com/api/v1/groups) へ、アクセストークンを含むAPIリクエストを送ります。
(6) リソースサーバは、アクセストークンを評価し、スコープ値に「okta.groups.read」が存在しているので問題ない (=リソースオーナーの認可が得られている) と判断して、グループ情報を返答します。
Client Credentialsグラントの設定
それでは、上記の流れを実現する設定を行いましょう。
Okta WICのAPI用アプリケーションの設定
Okta WICがOAuth 2.0 APIリクエストを受け取るためには、アプリケーションの設定が必要です。
「アプリケーション」→「アプリケーション」→「アプリ統合を作成」をクリックします。
「APIサービス」を選択して「次へ」をクリックします。
「アプリ統合名」に任意の値を入力してください。(ここでは「Okta Service-based API」としました。)
「保存」をクリックします。
「一般」タブで、「クライアントの資格情報」のクライアントIDの横のコピーボタンを押してコピーし、メモ帳などにペーストして一時的に保存しておいてください。後で利用します。
「編集」をクリックします。
クライアント認証は「公開鍵/秘密鍵」を選択してください。
「公開鍵」の構成は「Oktaにキーを保存」を選択して、「キーを追加」をクリックします。
すでにクライアント (マシン) 側が保有している秘密鍵/公開鍵ペアがあれば、その公開鍵をここにペーストして利用することもできます (むしろ、それが望ましいです) が、ここでは簡易的に、認可サーバであるOkta WIC側で秘密鍵/公開鍵ペアを生成する方法を選択します。
「新しいキーを生成」をクリックします。
「秘密鍵」で、(PEMでもよいのですが、ここでは)「JSON」を選択します。
「クリップボードにコピー」をクリックし、メモ帳などにペーストして一時的に保存しておいてください。後で利用します。
「公開鍵」の下にある「保存」をクリックします。
スクロールダウンして表示された「一般設定」の「編集」をクリックします。
「トークンリクエストのDPoP (Demonstrating Proof of Possession) ヘッダーを必須にする」のチェックを外して、「保存」をクリックします。
===== << Tips >> =====
実環境では、DPoP (Demonstrating Proof of Possession) が利用可能なクライアントであれば、是非とも利用すべき機能です。
DPoPを使うことで、アクセストークンが正当な所有者から送られてきたものかどうかを判断できるようになります。
もし、悪意ある第三者がアクセストークンを盗んだとしても、DPoPによって正当な所有者ではないことがわかるので、APIアクセスを拒否することができます。
ただ、簡単に説明できるような仕様ではないことと、クライアント側の下準備に少し手間がかかるので、本ブログ記事では対象から外しました。
===================
「Okta APIのスコープ」タブをクリックします。
「Okta APIのスコープ」では、APIで操作できる範囲をどうするのか (情報の読み出しだけなのか、設定変更もできるようにするのか、等) を決めます。
ここでは、クライアントに対して「グループとユーザーの情報提供だけを許可する」=「設定変更は許可しない」という要件を想定して、その設定を行います。
「okta.groups.read」と「okta.users.read」横にある「✔付与」をクリックして、「取り消す」の状態にしてください。
結果、以下の画面の「付与された」を選択すると、その二つだけが表示されていると思います。
「管理者ロール」タブ→「割り当てを編集」をクリックします。
ここでは、全ての管理者操作が実行できる「スーパー管理者」を割り当てます。
(理由: 後の動作確認の際に、権限の影響で情報が取得できないとAPIが正しく動作しているのかが分かりずらいため、ここでは広範囲の情報が取得できるような権限にしておきます。)
管理者設定を変更したので、このあと、Okta Admin Consoleのスーパー管理者の再認証が強制的に行われます。
JWTの生成
それでは、クライアントがトークンリクエストでOkta WICへ送るJWTを生成しましょう。
JWTのペイロードを作成する
JWTのペイロードとして、以下のようなJSON形式のテキストを準備します。
==========
{
"aud": "https://ご自身のOkta WICドメイン/oauth2/v1/token",
"iss": "クライアントID",
"sub": "クライアントID",
"exp": "Unixtimeの現在時刻+1時間 (秒で指定) "
}
==========
◾️ aud = Audienceの略で、「このJWTを受け取るのは誰なのか?」を指定します。
「https://ご自身のOkta WICドメイン/oauth2/v1/token」を指定します。
◾️ iss = Issuerの略で、「JWTの発行者」です。
「マシンが発行したJWTですよ」という意味合いで、Okta WICでコピーしたクライアントIDを指定します。
◾️ sub = Subjectの略で、「リソースオーナーを識別する値」です。
「マシンがリソースオーナーですよ」という意味合いで、上記のissと同じく、クライアントIDを指定します。
◾️ exp = Expirationの略で、「トークンの有効期限」です。
unixtime (1970年1月1日 0時0分0秒から経過した秒数) に最大で3600秒 (=1時間) を加えた値です。
===== << Tips >> =====
LinuxやmacOSでは、ターミナルに以下を入力することで、現在のunixtimeが得られます。
date +%s
このような↓値が得られますので、この値に3600を足して、exp値にしてください。
1732085074
===================
以下は、JWTペイロードのサンプルです。
==========
{
"aud": "https://xxxxx.okta.com/oauth2/v1/token",
"iss": "0oalhgzsXXX29TZr697",
"sub": "0oalhgzsXXX29TZr697",
"exp": "1732088674"
}
==========
JWTを生成する
下記のWebサイトを使って、簡易的にJWTを生成します。
Generate JWT:
「JWK KEY」側に◯がある状態で、「Okta Service-based API」アプリからコピーしておいた秘密鍵をペーストします。
「Payload:」の下に、先ほど用意したJWTペイロードをペーストします。
「Generate JWT」をクリックします。
以下のような画面が表示されます。これが生成されたJWTです。
「Click to copy!」をクリックし、メモ帳などにペーストして一時的に保存しておいてください。後で利用します。
===== << Tips >> =====
このJWT生成方法は、あくまでテストを目的とした方法です。
実際の利用に際しては、ご自身の環境にて、秘密鍵/公開鍵のペアの生成 (& 公開鍵だけをOkta WICに保存) し、ご自身のマシンに、上記のようなJWTを生成するロジックを組み込む必要があります。
[参考]: Build a JWT for Client Authentication
===================
「あれ?さっきJWTのペイロードは作ったけど、ヘッダは?」という疑問が浮かぶと思いますが、ヘッダは、先ほどのサイトでJWTを生成する際に、そのサイトが自動的に補ってくれています。
では、下記のサイトを使って、生成したJWTの中身を見てみましょう。
コピーしておいたJWTを、左側のEncodedの下にペーストしてみてください。
右側のDecodedを見ると、ヘッダ (HEADER)、ペイロード (PAYLOAD)、署名 (VERIFY SIGNATURE) が存在していることが分かります。
トークンリクエストの発行とアクセストークンの取得
JWTが生成できたので、クライアント (マシン) からトークンリクエストを発行してみましょう。
本ブログ記事では、マシンの代わりにmacOSのターミナルを利用して、curlでトークンリクエストを発行することにします。(Linuxでも同様の操作になると思います。)
「Client Credentialsグラントの流れ」のイメージ図で示した全ての値が必要ですので、curlコマンドでそれらを指定して発行します。
上記は画像ですが、コピー&編集して利用できるように、以下にテキストでも貼り付けておきます。
curl --location --request POST 'https://xxxxx.okta.com/oauth2/v1/token' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=okta.users.read okta.groups.read' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'client_assertion=eyJhbGciO〜〜〜U1-Ctg' | jq
このcurlコマンド発行の結果、以下のようなアクセストークンが得られるはずです。
「"access_token":」以降の "eyJra〜〜〜uXEyQ" がアクセストークンです。
APIリクエストの発行
アクセストークンが取得できたので、それを使ってAPIリクエストを発行してみましょう。
例として、Okta WICに設定されているグループの一覧をcurlで取得してみたいと思います。
下記のコマンドを実行してください。
上記は画像ですが、コピー&編集して利用できるように、以下にテキストでも貼り付けておきます。
curl -X GET "https://XXXXX.okta.com/api/v1/groups"\
-H "Accept: application/json"\
-H "Content-Type: application/json"\
-H "Authorization: Bearer eyJra〜〜〜uXEyQ" | jq
上記のcurlコマンドによってAPIリクエストが発行され、Okta WICに設定されているグループ情報の一覧が取得できると思います。
少なくとも、デフォルトで設定されている「Everyone」グループは取得できるはずです。
以下は、上記のcurlコマンドを実行して得られた結果のサンプルです。
「その他のAPIコマンドも発行してみたい」という場合には、[第6回] で設定したPostmanを見ていただくのが手っ取り早いと思います。
例えば下記は「ユーザーの一覧 (最大で25)」を取得する場合のコマンドです。
この例の場合ですと、上記のcurlコマンドで、Okta WICのURL (リソースサーバのURL) の「/api/v1/groups」を「/api/v1/users?limit=25」に変更するだけです。
以下は、そのcurlコマンドを実行して得られた結果のサンプルです。
私の環境ではユーザーとして「[email protected]」が登録されているので、その情報が取得できています。
[参考]:Implement OAuth for Okta with a service app
Test the Okta REST APIs with Postman
まとめ
本ブログ記事では、OAuth 2.0のClient Credentialsグラントを使った場合のOkta WIC APIの使い方をご紹介しました。
実環境で、マシンからOkta WICへAPIアクセスを行う際には、[第6回] でご紹介したトークン方式 (以降、APIキー) よりも、OAuth 2.0のご利用を推奨します。
OAuth 2.0 では「スコープ」を使ってAPI操作可能な範囲を制限できるので、APIキーよりも権限コントロールの柔軟性が高いです。
また、APIキーには「30日間有効」=「利用していない期間が30日を過ぎると無効になる」という固定された期限があるので、脅威に晒される期間が長いという点と、期限切れになると管理者が手動で再発行しなければならないという、管理者の手間がかかるという点にも課題があります。
OAuth2.0のアクセストークンにも有効期限はありますが、期限が切れても管理者が手動で再発行する必要はなく、クライアントから再発行処理を行えば、新しくアクセストークンが払い出されます。
管理面での柔軟性の乏しさや煩雑さは、設定の抜け/漏れを招くことがあるので、セキュリティの観点からも、実環境でのAPIのご利用はOAuth 2.0 をご利用になられた方が良いと考えます。
==========
Okta WICでは、認証の強化だけに留まらず、管理者が行うユーザーの登録/変更/削除に関わる業務の自動化や、人事管理システムや既設ディレクトリなどとの柔軟な連携も可能です。
Oktaでは、これらの機能を体感頂くことができる「Okta Essentials」トレーニングをご用意しています。(日本語でのトレーニングもご用意しています。)
このトレーニングは全体像を体系的にご理解頂ける内容となっていますので、是非ともご活用ください。