ここでは OpenID Connect を使った ID 連携をチュートリアル形式で実装していきます。
なお、本ドキュメントは SaaS サービスの Backend API と連携する Native App を通じて利用される SaaS サービスを対象としています。
Web ブラウザを通じて利用される SaaS サービスの場合は、本ドキュメントではなく OpenID Connect 実装チュートリアル (Web アプリ向け) をご覧ください。
なお、本チュートリアルではプログラミング言語に依存しない形での解説を行っていますが、実際に実装する際には各言語・フレームワークで広く使われている OAuth ライブラリ、OpenID Connect ライブラリの利用を推奨します。
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"] |
御社サービス側でユーザーの認証が必要になったタイミング (アプリ起動時や 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 には推測困難な乱数値 (Secure Random) を使用し、その値を Native App ローカルのセッションに紐付けて保存するようにしてください。
後述の Token Response 受信時に state のチェックを怠ったり当該セッションに紐付かない state を受け入れてしまうと、被害者が悪意ある攻撃者に攻撃者のアカウントで強制的にログインさせられる (いわゆる「ログイン CSRF」) 危険性があります。
ログイン CSRF 攻撃が成立してしまうと、被害者が攻撃者アカウントでログインした後に御社サービス上で生成した各種データ (メール送信履歴やクラウドに保存したドキュメント等) が、攻撃者の手に渡ってしまうことになります。
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 には推測困難な乱数値 (Secure Random) を使用し、その値を Native App ローカルのセッションに紐付けて保存するようにしてください。
code_challenge は code_verifier の SHA256 ハッシュ値を Base64 URL Encode (Base64 Encode 結果から、改行と =
を取り除いた上で、+
を -
に、/
を _
にそれぞれ置換) した値となります。
例えば code_verifier が 7823499fd8e7a73763e4e8ce00cb1bd3
の場合、code_challenge は 9F9PvYqHmv0Yo42FKBkoTfYI7LPeSoKWIoLxb75VieY
となります。
これらは OAuth PKCE という OAuth 拡張仕様で定義されているもので、主に redirect_uri に Custom Scheme URL を指定する (= redirect_uri を他のアプリに乗っ取られうる) Native App において、code が他のアプリに漏洩しても当該 code を悪用されないようにするために必要です。
なお、iOS の Universal Links のような仕組みにより redirect_uri として https
を使える場合は、code_challenge および code_verifier の利用は不要です。
OPTiM Store 側でのユーザー認証等が正常に終了すると、redirect_uri として指定された https://your-service.example.com/optim-store/callback
に code と state が付与された状態で、ユーザーがリダイレクトして戻されます。
ここでは以下のような値が 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 送信時のセッションに紐付いていることを確認してください。
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_token、refresh_token、id_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 は御社サービス側でのログイン処理に利用します。
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 の一連のフローは完了です。