外部 IdP との連携

はじめに

この文書は Authlete ベースの認可サーバー (AS) と外部のアイデンティティプロバイダー (IdP) との連携方法を説明するチュートリアルです。この連携により、Financial-grade API (FAPI) のような高いセキュリティの業界標準に準拠した API 認可基盤を構築すると同時に、外部 IdP のユーザー認証・管理機能を利用できるようになります。

この連携のしくみは、ユーザーの認証情報や属性情報などを IDaaS (Identity as a Service) を用いて既に維持管理しているサービス事業者が、英国オープンバンキング / オーストラリア消費者データ権 / ブラジルオープンバンキングなどのエコシステムが定義する FAPI ベースのセキュリティ要件を実装しなければならない場合に有用です。

このチュートリアルでは、OpenID Connect (OIDC) IdP として Okta、Authlete を利用する AS として java-oauth-server を用い、連携方法の例を説明します。

前提条件

このチュートリアルを始める前に次のアカウントを用意しておいてください。

また、java-oauth-server をインストールして実行する環境も必要となります。

AS と IdP の連携

OAuth 2.0 (RFC 6749) と OIDC (OpenID Connect Core 1.0) の仕様は、AS がユーザーをどのように認証するかについては意図的に定義していません。そのため AS は、ユーザー認証を外部 OIDC IdP に委譲し、当 IdP からユーザー情報を取得するという手段をとるかもしれません。下図は、そのような AS と外部 OIDC IdP との間の、アイデンティティ (ID) 連携のフローを示しています。

図に示したフローの各手順は以下の通りです。

  1. クライアントアプリケーションがウェブブラウザ経由で AS認可エンドポイント認可リクエストを送る。
  2. AS の認可エンドポイントは認可ページを生成してウェブブラウザに返す。AS が外部 IdP との ID 連携をサポートしていれば、認可ページに ID 連携用のリンクが含まれる。
  3. 表示された認可ページ内の ID 連携用のリンクをユーザーがクリックする。この操作により、選択された IdP との ID 連携を開始する Web API (図中の「(フェデレーション/開始)」) にウェブブラウザがアクセスする。
  4. Web API は OpenID Connect に準拠する認証リクエストを作成する。認証リクエストは IdP の認可エンドポイントを指す URL で、クエリー部にリクエストパラメーター群を含む。
  5. Web API は Location ヘッダーをつけて 302 Found (リダイレクションを促すものであれば何でもよい) をウェブブラウザに返す。これによりウェブブラウザは IdP の認可エンドポイントに認証リクエストを送ることになる。
  6. ウェブブラウザが IdP の認可エンドポイントに認証リクエストを送る。
  7. IdP の認可エンドポイントは認可ページを生成してウェブブラウザに返す。その認可ページは、「(クライアントではなく) AS が権限を要求している」とユーザーに伝える。
  8. ユーザーは IdP で自身のユーザー認証をおこない、AS からの認証リクエストを承認する。
  9. IdP は認可コードを生成し、Location ヘッダーをつけて 302 Found (リダイレクションを促すものであれば何でもよい) をウェブブラウザに返す。これによりウェブブラウザは (クライアントのものではなく) AS のリダイレクション URI に認可コード付きでアクセスする。IdP の視点からは、AS はクライアントアプリケーションであり、当該リダイレクション URI は AS が事前に IdP に登録しておいたものの一つである。
  10. AS のリダクレションエンドポイント (図中の「フェデレーション/コールバック」) が認可コードを受け取る。
  11. リダイレクションエンドポイントは、IdP のトークンエンドポイントに、受け取った認可コードを含むトークンリクエストを送る。
  12. IdP のトークンエンドポイントはアクセストークンID トークンを生成して返す。
  13. リダイレクションエンドポイントは返却されたアクセストークンを添えて IdP のユーザー情報エンドポイントにアクセスする。
  14. IdP のユーザー情報エンドポイントはユーザーの情報を返す。
  15. リダイレクションエンドポイントはユーザーが AS にログインしたとみなし、認可ページを再生成してウェブブラウザに返す。
  16. ユーザーはクライアントからの当初の認可リクエストを承認する。
  17. AS は認可コードを生成し、Location ヘッダーをつけて 302 Found (リダイレクションを促すものであれば何でもよい) をウェブブラウザに返す。これによりウェブブラウザは (AS のものではなく) クライアントのリダイレクション URI に認可コード付きでアクセスする。ここではクライアントが少なくとも一つのリダイレクション URI を事前に AS に登録済みであることを想定している。
  18. クライアントのリダクレションエンドポイント (図中の「リダイレクションエンドポイント」) が認可コードを受け取る。
  19. クライアントはリダイレクションエンドポイント経由で受け取った認可コードを添えて AS のトークンエンドポイントにアクセスする。
  20. AS のトークンエンドポイントはアクセストークンを生成してクライアントに返す。

次の節では、Okta を IdP として設定し、Authlete をバックエンドとして用いる AS (java-oauth-server) と連動させて上図のフローを実現する方法を紹介します。

IdP (Okta) の設定

OIDC 互換 IdP のクライアントアプリケーションとして AS が動作できるよう、Okta 上で稼働するサーバーに「App Integration」を作成する必要があります。

下表は App Integration 設定の要点を示しています。

項目
Sign-in method OIDC - OpenID Connect
Application Type Web Application
Sign-in redirect URIs http://localhost:8080/api/federation/callback/okta
Controlled access Allow everyone in your organization to access

Sign-in redirect URIs の値を見てお気付きかもしれませんが、このチュートリアルの後半でローカルマシン上のポート番号 8080 で AS を動かすことになります。また、その AS は /api/federation/callback/okta API を提供します。

次の画像群は App Integration 設定のスクリーンショットです。

App Integration 生成後、Okta はクライアント IDクライアントシークレットの組を生成します。次節でこの組を利用します。

AS (java-oauth-server) の設定

このチュートリアルでは java-oauth-server を AS として用います。java-oauth-server はオープンソースの AS サンプル実装で、Authlete をバックエンドとして利用します。

まず初めに java-oauth-server をダウンロードして authlete.properties を編集してください。少なくとも同ファイル内の service.api_keyservice.api_secret の値を変更する必要があります。

$ git clone https://github.com/authlete/java-oauth-server
$ cd java-oauth-server
$ vi authlete.properties

次に federations.json をテキストエディタで開いてください。このファイルに ID 連携の設定を記述します。内容の初期値は下記のようになっています。YOUR_COMPANYYOUR_CLIENT_IDYOUR_CLIENT_SECRET を Okta から割り当てられた実際の値で置き換えてください。federations.json の詳細については「付録 / federations.json」を参照してください。

{
  "federations": [
    {
      "id": "okta",
      "server": {
        "name": "Okta-hosted IdP",
        "issuer": "https://YOUR_COMPANY.okta.com"
      },
      "client": {
        "clientId": "YOUR_CLIENT_ID",
        "clientSecret": "YOUR_CLIENT_SECRET",
        "redirectUri": "http://localhost:8080/api/federation/callback/okta",
        "idTokenSignedResponseAlg": "RS256"
      }
    }
  ]
}

必要な設定は以上です。ローカルマシン上で java-oauth-server を起動してください。

$ docker-compose up
    # もしくは mvn jetty:run

次のようなログが確認できれば、java-oauth-server は準備完了です。

app_1  | [INFO] Started ServerConnector@7c5ae5c3{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
app_1  | [INFO] Started @41702ms
app_1  | [INFO] Started Jetty Server

動作確認

AS と IdP の ID 連携」で示した図では、クライアントは OIDC 認可コードフロー (response_type=code) を用いています。しかしここでは、Okta との ID 連携が機能していることを素早く確認するため、ID トークンのみを発行する OIDC インプリシットフロー (response_type=id_token) を使用します。

次の URL は認可リクエストです。CLIENT_ID をAuthlete から発行されたクライアント ID (Okta から発行されたものではない)、SERVICE_API_KEY を Authlete から発行されたサービス API キーで置き換え、URL をウェブブラウザのアドレスバーに貼り付けてください。

http://localhost:8080/api/authorization?client_id=CLIENT_ID&response_type=id_token&scope=address+email+openid+phone+profile&redirect_uri=https://api.authlete.com/api/mock/redirection/SERVICE_API_KEY&nonce=mynonce&state=mystate&prompt=login

次のような認可ページが表示されます。Authorize ボタンの上に表示されている「Okta-hosted IdP」リンクをクリックしてください。

ウェブブラウザは Okta が生成した次のような認可ページを表示します。Okta 上で稼働しているサーバーにログインする際に利用するログイン ID とパスワードを入力してください。

ウェブブラウザは java-oauth-server が生成した元々の認可ページを再表示します。この際、java-oauth-server はあなたがログイン済みと認識しています。Authorize ボタンを押してください。

java-oauth-server は ID トークンを発行し、ブラウザをリダイレクションエンドポイントにリダイレクトします。Authlete サーバー上で提供されているリダイレクションエンドポイントのモック実装 (/api/mock/redirection/サービスAPIキー) は受け取ったパラメーター群を表示します。

リクエストに暗号化されていない ID トークンが含まれていれば、リダイレクションエンドポイントはその ID トークンのペイロード部をデコードし、次のように表示します。

次の表は、発行された ID トークン内のクレーム群のデータ提供元を示しています。ユーザー属性に関する属性は全て Okta 由来です。

クレーム データ提供元
email Okta
email_verified Okta
family_name Okta
given_name Okta
locale Okta
name Okta
phone_number_verified Okta
preferred_username Okta
updated_at Okta
zoneinfo Okta
iss Authlete / Service 設定, issuer
sub java-oauth-server / Authlete の /api/auth/authorization/issue API に渡された subject パラメーター
aud Authlete / Client 設定, clientId (変更不可)
exp Authlete / ID トークンの有効期間終了時刻
iat Authlete / ID トークンの発行時刻
auth_time java-oauth-server / Authlete の /api/auth/authorization/issue API に渡された authTime パラメーター
nonce クライアント / nonce リクエストパラメーターの値
s_hash Authlete / state リクエストパラメーターの値に基づいて計算

sub クレームの値 (例内の "00ucabhbjLVphPrXF696@okta") がどこから来たのか気になられるかもしれません。この値は、Okta が返した sub クレームの値と文字列 "@okta" を連結した結果に過ぎません。連結は FederationEndpoint.javacreateUserEntity() メソッドで実行されています。これに大きな意味はなく、sub クレームの値をどのように決めるかはあなた次第です。

まとめ

本チュートリアルでは、Authlete ベースの認可サーバー (AS) と外部のアイデンティティプロバイダー (IdP) との連携パターンを、java-oauth-server と Okta を用いて、実際の動作を確認しました。

付録

実装の詳細

ID 連携の実装レベルの詳細については java-oauth-serverFederation.javaFederationEndpoint.java を調べることで理解できます。

OIDC IdP のユーザー情報エンドポイントからユーザー情報を受け取ったあと、java-oauth-server の現実装はメモリ上のユーザーデータベースにユーザーレコードを登録します。しかし、商用実装では他の選択肢もありうるでしょう。

例えば、OIDC IdP から得たユーザーデータを手元のデータベースに複製することを避け、かわりに IdP から発行されたアクセストークンを覚えておき、後ほど好きな時に IdP のユーザー情報エンドポイントから最新のユーザー情報を取り出せるようにするかもしれません。

そのような動作を実装するため、AS は IdP から発行されたアクセストークンを覚えておくデータベースを用意するかもしれません。その他にも、アクセストークンに任意のキー・バリュー群を関連付ける Authlete 独自の Extra Properties 機能を用い、IdP が発行したアクセストークンを Authlete に覚えておかせるという方法も考えられます (参照→『トークンに任意のプロパティをひもづける方法』)。

federations.json

federations.json は ID 連携の設定を記述するための設定ファイルです。java-oauth-server は最初に認可リクエストを受け取ったときに当ファイルを読み込みます。ファイル内に列挙された ID 連携は java-oauth-server の認可ページ内に表示されます。

ファイルのデフォルトの位置は "fderations.json" です。環境変数 FEDERATIONS_FILE およびシステムプロパティー federations.file を用いて他の場所を指定することができます。

federations.json の内容は、"federations" を最上位プロパティーとして持つ JSON オブジェクトでなければなりません。

{
  "federations": [
    "(ID 連携の設定群)"
  ]
}

"federations" 配列の各要素は ID 連携の設定を表し、"id""server""client" を最上位プロパティーとして持つ JSON オブジェクトです。

{
  "federations": [
    {
      "id": "(設定群内で一意となる識別子)",
      "server": {
        "(サーバー設定)"
      },
      "client": {
        "(クライアント設定)"
      }
    }
  ]
}

"id" の値は、java-oauth-server の次の API パス内の federationId 部で使用されます。

  • /api/federation/initiation/federationId
  • /api/federation/callback/federationId

"server" には対象の IdP の設定を記述します。値は JSON オブジェクトで、下表のプロパティー群を持ちます。

プロパティー 説明
name IdP の表示名。認可ページで利用されます。
issuer IdP の発行者識別子。この値は IdP のディスカバリードキュメント内の "issuer" の値と一致していなければなりません。java-oauth-server の ID 連携を機能させるためには、IdP が {issuer}/.well-known/openid-configuration でディスカバリードキュメントを公開していなければなりません。

"client" はクライアントの設定を記述するもので、ここでクライアントは常に java-oauth-server を指します。外部 IdP から見ると java-oauth-server はクライアントアプリケーションであることに留意してください。"client" の値は JSON オブジェクトで、下表のプロパティー群を持ちます。

プロパティー 説明
clientId IdP が発行したクライアント ID。
clientSecret IdP が発行したクライアントシークレット。このプロパティーがセットされている場合、IdP のトークンエンドポイントに送られるトークンリクエストにクライアント認証用の Authorization ヘッダーが含まれるようになります。この動作はトークンエンドポイントがクライアント認証方式として client_secret_basic をサポートしていることを前提としています。
redirectUri IdP に事前登録してあるリダイレクション URI。
idTokenSignedResponseAlg IdP が ID トークンに署名する際に使用するアルゴリズム。このプロパティーが省略された場合、デフォルト値として "RS256" が使用されます。