OpenID Connect 実装チュートリアル

ここでは OpenID Connect を使った ID 連携をチュートリアル形式で実装していきます。

なお、本ドキュメントは SaaS サービスの Backend API と連携する Native App を通じて利用される SaaS サービスを対象としています。
Web ブラウザを通じて利用される SaaS サービスの場合は、本ドキュメントではなく OpenID Connect 実装チュートリアル (Web アプリ向け) をご覧ください。

なお、本チュートリアルではプログラミング言語に依存しない形での解説を行っていますが、実際に実装する際には各言語・フレームワークで広く使われている OAuth ライブラリ、OpenID Connect ライブラリの利用を推奨します。

Step0. OpenID Connect Client の登録

OPTiM Store では、Tenant Contract API および OpenID Connect Client Registration API を利用して、OpenID Connect Client の登録を自動化していますが、本ドキュメントでは事前にそれらの API を利用して、もしくは手動にて以下のように OpenID Connect Client を登録済であると仮定します。

Key Value
client_id 7a05a846d3dc5a860013ea7fe201e025
client_secret d9fccaa5cdf30f86c6efe73f25774e9a625ceaa9aa26de7e9f364370a71820d5
redirect_uris ["https://your-service.example.com/optim-store/callback"]

Step1. Authorization Request の送信

御社サービス側でユーザーの認証が必要になったタイミング (アプリ起動時や Dashboard へのアクセス時 etc.) で、https://store-xyz-federation.optim.co.jp/connect/authorization に OpenID Connect の定める以下のパラメータを付与した URL を、外部ブラウザで開きます。

Key Value
client_id 7a05a846d3dc5a860013ea7fe201e025
response_type code
scope openid
redirect_uri https://your-service.example.com/optim-store/callback
state クライアントサイドのセッションに紐付いた値 (この例では 590d36e1688aa02f863c78cae78d5dc8 とします)
nonce バックエンドのセッションに紐付いた値 (この例では 72cf7cf3a0010c45912ce3562d25e2c3 とします)
code_challenge 動的に生成したテンポラリ鍵 (code_verifier) の SHA256 ハッシュを Base64 URL Encode した値

実際にリダイレクト先になる URL は以下のようになります。

https://store-xyz-federation.optim.co.jp/connect/authorization?client_id=7a05a846d3dc5a860013ea7fe201e025&response_type=code&scope=openid&redirect_uri=https%3A%2F%2Fyour-service.example.com%2Foptim-store%2Fcallback&state=590d36e1688aa02f863c78cae78d5dc8&nonce=72cf7cf3a0010c45912ce3562d25e2c3&code_challenge=9F9PvYqHmv0Yo42FKBkoTfYI7LPeSoKWIoLxb75VieY

なお、外部ブラウザとして iOS の場合は Safari or SFSafariViewController、Android の場合は Chrome or Chrome Custom Tab をお使いいただけます。
セキュリティ上の理由から、WebView の利用は禁止します。

state の生成方法

state には推測困難な乱数値 (Secure Random) を使用し、その値を Native App ローカルのセッションに紐付けて保存するようにしてください。

後述の Token Response 受信時に state のチェックを怠ったり当該セッションに紐付かない state を受け入れてしまうと、被害者が悪意ある攻撃者に攻撃者のアカウントで強制的にログインさせられる (いわゆる「ログイン CSRF」) 危険性があります。
ログイン CSRF 攻撃が成立してしまうと、被害者が攻撃者アカウントでログインした後に御社サービス上で生成した各種データ (メール送信履歴やクラウドに保存したドキュメント等) が、攻撃者の手に渡ってしまうことになります。

nonce の生成方法

nonce には推測困難な乱数値 (Secure Random) を使用し、その値を Native App と Backend API Server の間で確立されたセッションに紐付けて保存するようにしてください。
なお nonce の生成は Backend API Server 側で行うようにしてください。

Native App から Authorization Request を送信する毎に、Native App が都度 Backend API Server に対してセッションを確立しに行き、バックエンドサーバー側でセッションに紐付いた nonce を生成した上でその値を Native App に返すようにすると良いでしょう。

code_verifier および code_challenge の生成方法

code_verifier には推測困難な乱数値 (Secure Random) を使用し、その値を Native App ローカルのセッションに紐付けて保存するようにしてください。

code_challengecode_verifier の SHA256 ハッシュ値を Base64 URL Encode (Base64 Encode 結果から、改行と = を取り除いた上で、+- に、/_ にそれぞれ置換) した値となります。

例えば code_verifier7823499fd8e7a73763e4e8ce00cb1bd3 の場合、code_challenge9F9PvYqHmv0Yo42FKBkoTfYI7LPeSoKWIoLxb75VieY となります。

これらは OAuth PKCE という OAuth 拡張仕様で定義されているもので、主に redirect_uri に Custom Scheme URL を指定する (= redirect_uri を他のアプリに乗っ取られうる) Native App において、code が他のアプリに漏洩しても当該 code を悪用されないようにするために必要です。

なお、iOS の Universal Links のような仕組みにより redirect_uri として https を使える場合は、code_challenge および code_verifier の利用は不要です。

Step2. Authorization Response の受信

OPTiM Store 側でのユーザー認証等が正常に終了すると、redirect_uri として指定された https://your-service.example.com/optim-store/callbackcodestate が付与された状態で、ユーザーがリダイレクトして戻されます。

ここでは以下のような値が code として返されるものとします。

Key Value
code f467ccbaca74f35da15c265616a059cc1ea7a31d2cde0a119e5e8d6714cc3e68

すると、ユーザーは以下の URL にリダイレクトされ、御社 Native App に戻されます。

https://your-service.example.com/optim-store/callback?code=f467ccbaca74f35da15c265616a059cc1ea7a31d2cde0a119e5e8d6714cc3e68&state=590d36e1688aa02f863c78cae78d5dc8

前述の通り、ログイン CSRF 攻撃を防止するため、Authorization Response を受けとったらまず state が Authorization Request 送信時のセッションに紐付いていることを確認してください。

Step3. Token Request の送信 & Token Response の受信

state のチェックが終わったら、受け取った code を Backend API Server に POST します。
OAuth PKCE 対応を行っている場合は、同時に code_verifier も Backend API Server に POST します。

なお、Token Request を Native App から直接 OPTiM Federation Server に送信することは禁止します。
そのようなことをすると、Native App に client_secret を埋め込む必要が出るため、client_secret の漏洩につながります。

code (および code_verifier) を受け取った Backend API Server は、https://store-xyz-federation.optim.co.jp/connect/token にそれらを POST して、access_tokenrefresh_tokenid_token を取得します。

この時送信するパラメータは、以下の通りです。

Key Value
client_id 7a05a846d3dc5a860013ea7fe201e025
client_secret d9fccaa5cdf30f86c6efe73f25774e9a625ceaa9aa26de7e9f364370a71820d5
grant_type authorization_code
code f467ccbaca74f35da15c265616a059cc1ea7a31d2cde0a119e5e8d6714cc3e68 (実際に受け取った値を指定してください)
redirect_uri https://your-service.example.com/optim-store/callback
code_verifier 7823499fd8e7a73763e4e8ce00cb1bd3

実際に送信される HTTP POST Request は、以下のようになります。

POST /connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
HOST: store-xyz-federation.optim.co.jp

client_id=7a05a846d3dc5a860013ea7fe201e025&client_secret=d9fccaa5cdf30f86c6efe73f25774e9a625ceaa9aa26de7e9f364370a71820d5&grant_type=authorization_code&code=f467ccbaca74f35da15c265616a059cc1ea7a31d2cde0a119e5e8d6714cc3e68&redirect_uri=https%3A%2F%2Fyour-service.example.com%2Foptim-store%2Fcallback&code_verifier=7823499fd8e7a73763e4e8ce00cb1bd3

レスポンスとしては以下のような JSON データが返されます。

{
  "access_token": "eyJ..(省略)..oxA",
  "refresh_token": "413..(省略)..873",
  "token_type": "bearer",
  "expires_in": 300,
  "id_token": "eyJ..(省略)..LeA"
}

通常はここで受け取った access_token および refresh_token を利用することはありませんので、それらはそのまま破棄していただいて結構です。

id_token は御社サービス側でのログイン処理に利用します。

Step5. ID Token の検証およびログイン処理

ID Token の検証方法については ID Token の検証 - OpenID Connect Federation にまとめてあるので、そちらをご覧ください。

また上記ドキュメントに加え、Native App の場合は ID Token に nonce も含まれているため、その値が Authorization Request 送信時のセッションに紐付いた nonce 値と一致することを確認してください。
この値が異なる場合、どこかの段階で code が他のセッションで発行されたものとすり替えられているため、エラーとしてください。

ID Token 検証が成功したら、Backend API Server 側でのユーザー認証が完了します。
必要に応じて Backend API Server から Native App に対して御社 API 用の Access Token 等を発行してください。

以上で、OpenID Connect による Federation の一連のフローは完了です。