KeycloakのOpen ID Connect認証で取得したリフレッシュ・トークンを使って、アクセス・トークンを更新する実装を見かけたので、Azure ADで同様の実装を試してみました。
以下から始まる一連の記事で行った実装への追加になります。
Microsoft OneDriveを操作するAPEXアプリの作成(1) - Azure ADへのアプリ登録リフレッシュ・トークンは、Azure ADを使ったOpen ID Connect認証の応答であるJSONデータに含まれています。Oracle APEXの認証スキームの認証後のプロシージャに、リフレッシュ・トークンの取得と保存を行うコードを記述します。
取得したリフレッシュ・トークンを保存するために、アプリケーション・アイテムとしてG_REFRESH_TOKENを作成します。
共有コンポーネントのアプリケーション・アイテムを開きます。
作成をクリックし、アプリケーション・アイテムG_REFRESH_TOKENを作成します。有効範囲はアプリケーション、セキュリティのセッション・ステート保護はデフォルトの制限付き - ブラウザから設定不可のまま変更しません。トークンにエスケープされるような特殊文字は含まれないと考えているので、特殊文字をエスケープはオンにしています。リフレッシュ・トークンを画面に表示することはないため、オフにしても問題ないでしょう。
アプリケーション・アイテムの作成をクリックします。
アプリケーション・アイテムが作成されます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
procedure post_auth is | |
begin | |
:G_REFRESH_TOKEN := apex_json.get_varchar2('refresh_token'); | |
end post_auth; |
ソースに記述したプロシージャpost_authを、ログイン・プロセスの認証後のプロシージャ名に設定します。
変更の適用をクリックして、保存します。
ワークスペース・ユーティリティのWeb資格証明を開きます。
すでにOpen ID Connectによる認証に使用しているWeb資格証明が登録済みです。これは認証タイプがOAuth2クライアント資格証明フローです。
同じクライアントID、クライアント・シークレットを使った基本認証のWeb資格証明を作成します。リフレッシュ・トークンを使ったアクセス・トークンの取得に、このWeb資格証明を使います。
クライアントの資格情報を開き、クライアント・シークレットの値を取得します。作成直後を除きコピーできないことから、再作成が必要な場合もあります。
この値もコピーしておきます。
リフレッシュ・トークンを発行するには、APIのアクセス許可(いわゆるスコープ - scope)にoffline_accessが含まれている必要があります。
クライアント・シークレットを再作成した場合は、認証タイプがOAuth2クライアント資格証明フローのWeb資格証明のクライアント・シークレットまたはパスワードを更新します。
認証タイプを基本認証としたWeb資格証明を作成します。
名前はMS Azure Cred Basic、静的IDはMS_AZURE_AD_BASICとしています。認証タイプに基本認証を選択し、クライアントIDまたはユーザー名、クライアント・シークレットまたはパスワードは、OAuth2クライアント資格証明フローと同じ情報を設定します。
実際にアクセス・トークンをリフレッシュする実装を行います。
ホーム・ページにリフレッシュを行うボタンRefresh Tokenを作成します。
サーバー側の条件のボタン押下時にREFRESH_TOKENを指定します。
ページ・デザイナでホーム・ページを開き、ボタンを作成します。識別のボタン名はREFRESH_TOKEN、ラベルはRefresh Token、動作のアクションはデフォルトのページの送信を選択します。
プロセス・ビューを開き、ボタンREFRESH_TOKENをクリックしたときに実行するプロセスを作成します。
識別の名前はRefresh Token、タイプとしてコードを実行を選択します。ソースのPL/SQLコードとして以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_refresh_token varchar2(4000) := :G_REFRESH_TOKEN; | |
l_response clob; | |
l_response_json json_object_t; | |
l_access_token varchar2(4000); | |
l_expires_in_sec number; | |
C_CRED_OAUTH2 constant varchar2(20) := 'MS_AZURE_AD'; | |
C_CRED_BASIC constant varchar2(20) := 'MS_AZURE_AD_BASIC'; | |
begin | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Content-Type','application/x-www-form-urlencoded',p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => 'https://login.microsoftonline.com/common/oauth2/v2.0/token' | |
,p_http_method => 'POST' | |
,p_body => 'grant_type=refresh_token' || chr(38) || 'refresh_token=' || l_refresh_token | |
,p_credential_static_id => C_CRED_BASIC | |
); | |
l_response_json := json_object_t.parse(l_response); | |
/* アクセス・トークンのアップデート */ | |
l_access_token := l_response_json.get_string('access_token'); | |
l_expires_in_sec := l_response_json.get_number('expires_in'); | |
if l_access_token is not null then | |
apex_credential.set_session_token( | |
p_credential_static_id => C_CRED_OAUTH2 | |
,p_token_type => APEX_CREDENTIAL.C_TOKEN_ACCESS | |
,p_token_value => l_access_token | |
,p_token_expires => (sysdate + l_expires_in_sec/86400) | |
); | |
apex_debug.info('Access Token is updated by %s', substr(l_access_token, 1,20)); | |
else | |
apex_debug.info('Access Token is not updated.'); | |
end if; | |
/* リフレッシュ・トークンのアップデート (もしあれば) */ | |
l_refresh_token := l_response_json.get_string('refresh_token'); | |
if l_refresh_token is not null then | |
:G_REFRESH_TOKEN := l_refresh_token; | |
/* | |
* apex_credential.set_session_tokenでリフレッシュ・トークンを保存するオプションが | |
* あるので呼び出しているが、今のところ使い道はないはず。 | |
* リフレッシュ・トークンの有効期間はSPAであれば24時間、それ以外は90日とのことなので、 | |
* APEXでは90日なはずだが、1日としている。 | |
* https://learn.microsoft.com/ja-jp/azure/active-directory/develop/refresh-tokens | |
*/ | |
apex_credential.set_session_token( | |
p_credential_static_id => C_CRED_OAUTH2 | |
,p_token_type => APEX_CREDENTIAL.C_TOKEN_REFRESH | |
,p_token_value => l_refresh_token | |
,p_token_expires => (sysdate + 1) | |
); | |
apex_debug.info('Refresh Token is updated by %s', substr(l_refresh_token, 1,20)); | |
else | |
apex_debug.info('Refresh Token is not updated.'); | |
end if; | |
end; |
以上で、アクセス・トークンを更新するボタンができました。
動作を確認してみます。
アプリケーションを実行し、Azure ADを使ってサインインをします。サインインに成功すると、ホーム・ページが開きます。
以下のMS Graph APIを呼び出し、ドライブの一覧を表示します。
https://graph.microsoft.com/v1.0/me/drives
認証スキームで使用しているWeb資格証明(タイプがOAuth2クライアント資格証明フロー)を開きます。ストアド・トークンのアクセス・トークン(セッション)が1、つまりアクセス・トークンが保存されていることを確認します。
トークンのクリアをクリックします。
ページがWeb資格証明の一覧に戻るので、再度、対象のWeb資格証明を開きます。
アクセス・トークン(セッション)が0になっています。
ボタンSubmitをクリックすると無効なURLというエラーが発生します。アクセス・トークンが無効(正確にいうと無くなっている)になっているので、ボタンRefresh Tokenをクリックし、アクセス・トークンを再取得します。
Web資格証明のページを再ロードすると、アクセス・トークン(セッション)が1になっており、新たにアクセス・トークンが保存されていることが確認できます。
再度MS Graph APIを呼び出すと、今度はAPIが正常に応答し、アクセス・トークンが有効であることが確認できます。
APEXアプリケーションの最大セッションの長さ(秒)はデフォルトで通常3600秒なので、このままの設定であればリフレッシュ・トークンを使ってアクセス・トークンを更新する意味はそれほどない(アクセス・トークンの有効期間も3600秒であるため)かもしれません。
APEXアプリケーションの最大セッション長は、アプリケーション定義のセキュリティのセッション管理の最大セッションの長さ(秒)を変更することで、長くすることができます。
以上になります。
以前に公開しているアプリケーションのエクストポートに、今回の実装を追加しました。
https://github.com/ujnak/apexapps/blob/master/exports/onedrive-operation.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完