LTI詳細編  ④LTI Coreの通信フロー

# はじめに
LTIとはLearning Tools Interoperability（学習ツールの相互運用性）の略称であり、LMS（学習管理システム）を始めとする学習用プラットフォームと教材・学習ツールを連携させるための国際技術標準です。
教育関連のシステムやアプリケーションの利用者、導入者、開発・提供者それぞれにとってLTIを活用することには様々なメリットがあります。
今回はLTIの概要・詳細について、次の4つの記事を作成しました。
**LTI基礎編**
記事①[LTIの基本](https://qiita.com/Hiroki_Kobari/items/2419299342367a9f2535)
記事②[利用の流れ（連携初期設定～ツール呼び出しまで）](https://qiita.com/Hiroki_Kobari/items/a68026faa3a9cbaeff2c)
記事③[押さえておきたいキーワード](https://qiita.com/Hiroki_Kobari/items/75e216b7be6b6cadb430)

**LTI詳細編**
記事④LTI Coreの通信フロー ⇒ 本記事

本記事は4つ目のLTI詳細編④LTI Coreの通信フローになります。
ここでは、ツールを起動するまでに行われている各処理について、詳細に説明していきます。

※本記事では、次の用語は以下の内容を指しています。
- LTI ⇒ LTI 1.3
- プラットフォーム ⇒ LMS
※一般的には、LMSがプラットフォームになることが多いですが、必ずしもLMSである必要はありません。
- ツール ⇒ 教材や学習コンテンツなどの学習ツール

# 1. LTI Coreについて
LTI CoreとはLTIの基礎部分を構成する仕様のことで、シングルサインオン（SSO）やツールとの連携といったLTIの基本となる機能を提供します。これを拡張したものが[LTI概要編①LTIの基本 1.3章](https://qiita.com/Hiroki_Kobari/private/2419299342367a9f2535#13-lti-advantage%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)で説明しているLTI Advantageになります。

# 2. LTI Core通信フローの詳細
LTI概要編の記事で少し触れたように、LTIの認証はOpenID Connect（OIDC）がベースになっています。
以下の図は、LTI Core（ResourceLinkRequest）によるツール起動までのフローを表した図になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725519/215e5f03-f2de-2e71-a4e8-f5e99291346e.png)

プラットフォームからツールを起動するまでのフローは、大きく3つのパートに分けられます。
1. OIDC①（Platform⇒Tool）
プラットフォームからツールを起動する際、最初にツールのInitiate Login URLにアクセスしてOIDC認証を開始します。
（※LTIではプラットフォームがIDプロバイダーの役割を果たすことが多く、プラットフォームが起点となってフローが始まるため、このパートはOIDCにおける[4. Initiating Login from a Third Party](https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#ThirdPartyInitiatedLogin)にあたります。）

1. OIDC②（Tool⇒Platform）
プラットフォームはツールから送信された情報を使用してツールの認証を行います。認証に成功した場合、プラットフォームはログインしているユーザーの情報などを含むIDトークンを発行します。

1. ResourceLinkRequest（Platform⇒Tool）
プラットフォームは発行したIDトークンをツールに送り、ツールは受け取ったIDトークンの検証を行います。検証が完了すると、目的のツール画面が表示されます。

次の章からは、それぞれのパートの処理や必要となる情報などについて詳細に説明していきます。

## 2.1. OIDC①（Platform⇒Tool）
まずは、LTIによる認証を開始する際の処理について説明します。
この処理は、ユーザーがプラットフォーム上に設置されているLTIリンクからツールを起動しようとした際に、最初に行われる処理です。
ユーザーがLTIリンクをクリックすると、プラットフォームはUA（ユーザーエージェント）をツールのログインエンドポイント（[記事②4章](https://qiita.com/Hiroki_Kobari/items/a68026faa3a9cbaeff2c#4-%E3%83%84%E3%83%BC%E3%83%AB%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97)の①Tool OIDC Login Initiation URL）にリダイレクトさせることによって、OIDCフローを開始します。リクエストの形式はGET/POSTのどちらでも問題ありません。
このリクエストに含まれているパラメーターについては、次のようなものがあります。
（※本記事の表について、黒字で記載しているのはOIDCで定義されているもの、赤字で記載しているのはLTIで追加されたものになります。）
|パラメーター  |必須/任意　|概要  |
|---|---|---|
|iss  |必須|プラットフォームを識別するための値|
|login_hint  |必須|ユーザーがログイン時に使用するログイン識別子のヒント<br>（例）ユーザーのUUID、ログインIDなど|
|target_link_uri  |必須|LTI通信フローの最後に実行される実際のエンドポイント|
|<font color="Red">lti_deployment_id|任意|これを含む場合、後続のLTIメッセージのクレーム[*注1]で渡されるのと同じデプロイメントID[*注2]にする必要がある|
|<font color="Red">client_id|任意|後続のLTIメッセージリクエストの認可に使用されるクライアントID[*注3]を指定するための値|
|<font color="Red">lti_message_hint |任意|起動される実際のメッセージを特定するLTI固有のパラメーターで、これを含む場合は後続の認証リクエストにも同じ値を含める必要がある|

>*注1：https://purl.imsglobal.org/spec/lti/claim/deployment_id を指し、詳細については後述します。
*注2：デプロイメントIDについては、[こちら](https://qiita.com/Hiroki_Kobari/private/75e216b7be6b6cadb430#deployment-id)を参照してください。
*注3：クライアントIDについては、[こちら](https://qiita.com/Hiroki_Kobari/private/75e216b7be6b6cadb430#client-id)を参照してください。

## 2.2. OIDC②（Tool⇒Platform）
#### 認証リクエスト
次に、ツールはOIDC①で送られてきた情報を基にリクエストを作成し、UAをプラットフォームの認証エンドポイント（[記事②4章](https://qiita.com/Hiroki_Kobari/items/a68026faa3a9cbaeff2c#4-%E3%83%84%E3%83%BC%E3%83%AB%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97)の②Platform OIDC Auth URL）にリダイレクトさせます。リクエストの形式はOIDC①と同様でGET/POSTのどちらでも問題ありません。
この認証リクエストに含まれているパラメーターには、次のようなものがあります。
|パラメーター  |必須/任意　|概要  |
|---|---|---|
|scope|必須|アクセストークンに紐づくアクセス権を指定する値であるが、OIDCの場合はopenid（固定値）|
|reponse_type |必須|使用される認可フローを決定するための値で、ここではid_token（固定値）|
|client_id  |必須|プラットフォームがツールを識別するための値|
|redirect_uri|必須|プラットフォームで発行されたIDトークンの受け渡し先となるツールのエンドポイントを指定するための値|
|login_hint|必須|OIDC①のlogin_hintで渡されたものと同じ値|
|state|任意|ユーザーのセッションと紐づけて管理することでCSRFを防止するための値|
|response_mode|必須|トークンは長くなる可能性があり、POSTとして渡す必要があるため、ここではform_post（固定値）|
|nonce|必須|ここで指定した値がIDトークン中のnonceにも記載され、認証リクエストと対応するIDトークンであることを証明することで、リプレイ攻撃を防止するための値|
|prompt|必須|プラットフォームに対してUIの動作を指定するための値で、ここではnone（固定値）|
|<font color="Red">lti_message_hint|任意[*注4]|OIDC①のlti_message_hintで渡されたものと同じ値|

>*注4：OIDC①のパラメーターにlti_message_hintが含まれている場合は必須になります。

#### 認証リクエストの検証
認証リクエストを受けたプラットフォームは、そのリクエストが正当なものであるかの検証を行います。認証リクエストに含まれるパラメーターを基に、プラットフォームはredirect_uriがclient_idで示されたツールの有効なエンドポイントであるか、現在ログインしているユーザーがlogin_hintと一致するかを検証した後、IDトークンを発行します。
IDトークンとはツールがユーザーの認証を行うために使うものです。このIDトークンには、ユーザーを識別するためのIDなど始めとして様々な情報が含まれているとともに、改ざんを防止するための署名がされています。この署名にはプラットフォームの秘密鍵が使われます。

## 2.3. ResourceLinkRequest（Platform⇒Tool）
#### 認証レスポンス
最後に、UAをOIDC②で指定されたredirect_uriにリダイレクトさせ、認証レスポンスで発行したIDトークンをツールに送ります。このときの認証レスポンスはPOSTで送る必要があり、含まれているパラメーターは次のようになっています。
|パラメーター  |必須/任意　|概要  |
|---|---|---|
|state|必須|ツールはこの値がOIDC②で送ったstateと一致することを確認する必要がある|
|id_token|必須|ユーザー認証に使う署名つきのJSON Web Token（JWT）|


#### IDトークンについて
ここからは、認証レスポンスで受け取ったIDトークンの中身について説明していきます。IDトークンは表中に記載したようにJWTの形式になっており、JWTに含まれる項目はClaim（クレーム）と呼ばれます。JWTは「ヘッダー」「ペイロード」「署名」の3パートから構成されており、それぞれのパートは.（ドット）で区切られ、それをBase64 URLエンコードした文字列になっています。ここからはそれぞれのパートについて説明していきます。

##### ヘッダー
ヘッダー部分には、次のようなクレームが含まれており、これらは後の署名検証に使用されます。
|クレーム  |必須/任意　|概要  |
|---|---|---|
|typ|必須|トークンの形式を示す値で、ここではJWT（固定値）|
|alg|必須|署名に使われているアルゴリズムを示す値[*注5]|
|kid|任意|プラットフォームの公開鍵のID|

>*注5：[こちら](https://www.imsglobal.org/spec/security/v1p1#approved-jwt-signing-algorithms)で記載されているアルゴリズムのいずれかを使用する必要があります。

##### 署名
実際のJWTの中身とは順序が前後しますが、先に署名部分について説明します。
（※実際のJWTは「ヘッダー」.「ペイロード」.「署名」の順番になっています。）
名前の通り、署名部分にはヘッダーのalgクレームで指定されたアルゴリズムを使って作成した署名が入っています。ツールは、ヘッダーのkidクレームで指定されたIDを使ってプラットフォームの公開鍵を取得し、署名の検証を行います。

##### ペイロード
ペイロード部分には、ユーザーを識別するための情報やIDトークンの正当性を確認するための情報など、重要なクレームが多く含まれています。具体的には次のようなクレームがあります。
（※ここに記載したのは代表的なものだけで、これ以外にもOIDCやLTIで定義されているクレームがあります。）

|クレーム  |必須/任意　|概要  |
|---|---|---|
|iss|必須|IDトークンの発行者であるプラットフォームを識別するための値|
|aud|必須|IDトークンを受け取るツールのクライアントID|
|sub|必須|ユーザーを識別するための値|
|exp|必須|IDトークンの有効期限|
|iat|必須|IDトークンの発行時刻|
|nonce|必須|OIDC②に含まれるnonceと同じ値|
|azp|任意|認可対象のツールのクライアントID|
|name</font>|任意|氏名|
|given_name|任意|名|
|family_name|任意|姓|
|middle_name|任意|ミドルネーム|
|picture|任意|プロフィール画像のURL|
|email|任意|メールアドレス|
|<font color="Red">message_type[*注6]|必須|LTIメッセージのタイプを示す値で、今回はLtiResourceLinkRequest（固定値）|
|<font color="Red">version|必須|LTIのバージョンを示す値で、今回は1.3.0（固定値）|
|<font color="Red">deployment_id|必須|デプロイメントID|
|<font color="Red">target_link_uri|必須|OIDC①で指定されたtarget_link_uriと同じ値|
|<font color="Red">resource_link|必須|3つのパラメーター（id[*注7], title, description）で構成されるリソースリンクのプロパティ|
|<font color="Red">roles|必須|ユーザーが持つロールのURI値の配列[*注8]|
|<font color="Red">context|任意|4つのパラメーター（id[*注9], type, label, title）から構成されるリソースリンクが設置されているコンテキストのプロパティ|
|<font color="Red">tool_platform|任意|起動を開始するプラットフォームインスタンスに関連するプロパティ|
|<font color="Red">role_scope_mentor|任意|ユーザーがメンターとしてアクセスできるユーザーID値の配列|
|<font color="Red">launch_presentation|任意|どのようにコンテンツを表示するか（ウィンドウやフレームの種類、高さ/幅など）を指定するためのプロパティ|
|<font color="Red">lis|任意|利用可能なLIS[*注10]に関するプロパティ|
|<font color="Red">custom|任意|開発者が独自に追加することのできるkey-value形式のカスタムプロパティ|

>*注6：これ以降に記載するクレームは、実際にはhttps://purl.imsglobal.org/spec/lti/claim/ に続くURL形式で表現されますが、ここでは省略して記載しています。
（例）https://purl.imsglobal.org/spec/lti/claim/message_type
*注7：3つの中でidだけが必須のパラメーターであり、[resource_link_id](https://qiita.com/Hiroki_Kobari/private/75e216b7be6b6cadb430#resource-link--resource-link-id)と呼ばれます。
*注8：[こちら](https://www.imsglobal.org/spec/lti/v1p3/#role-vocabularies)にLTIで使用されるロールの一覧が記載されており、この中から少なくとも1つのロールが含まれている必要があります。
*注9：4つの中でidだけが必須のパラメーターであり、[context_id](https://qiita.com/Hiroki_Kobari/private/75e216b7be6b6cadb430#context--context-id)と呼ばれます。
*注10：学習情報サービス（Learning Information Service）の略称です。

#### IDトークンの検証
IDトークン中に含まれる情報と、ここまでのOIDC①・②の情報を基に、ツールはIDトークンの検証を行っていきます。具体的な検証内容[*注11]としては、次のようなものがあります。
1. 署名の検証
プラットフォームの公開鍵を使ってIDトークンの署名検証を行います。このとき、プラットフォームの公開鍵は[記事② 2. 初期設定](https://qiita.com/Hiroki_Kobari/items/a68026faa3a9cbaeff2c#%E3%83%84%E3%83%BC%E3%83%AB%E5%81%B4%E3%81%AB%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8B%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AE%E6%83%85%E5%A0%B1)でツールに登録しておいたPlatform JWKS URLから取得します。

1. 発行者の検証
事前にツール側に登録されているプラットフォームのIssuer IDとIDトークン中のissの値が一致することを確認します。

1. 受信者の検証
IDトークン中ののaudがissで識別されるプラットフォームで発行されたclient_idの値を含んでいることを確認します。

1. 署名アルゴリズムの検証
IDトークンのヘッダーに含まれるalgの値が[こちら](https://www.imsglobal.org/spec/security/v1p1#approved-jwt-signing-algorithms)に記載されている承認されたアルゴリズムのいずれかであることを確認します。

1. 有効期限の検証
現在の時刻がIDトークン中のexpで表される時刻より前であることを確認します。

1. リプレイ攻撃の検証
OIDC②のnonceとIDトークン中のnonceの値が一致すること、まだこのnonceの値を受け取っていないことを確認します。

>*注11：記載している検証内容は、[1EdTech Security Framework v1.1 #5.1.3 Authentication Response Validation](https://www.imsglobal.org/spec/security/v1p1/#authentication-response-validation)から一部抜粋したものになります。

#### LTIリソースの表示
すべての検証が完了し、正しいIDトークンであることが証明されると、ツールはIDトークン中のtarget_link_uriで指定されたコンテンツの画面を表示し、一連の処理は完了となります。

以下の画像は、オープンソースのLMSであるMoodle（プラットフォーム）から1EdTechが公開しているブロック崩しゲーム（ツール）を起動したときのものです。プラットフォーム上にあるLTIリンクをクリックすると、本記事で説明した通信フローが実行され、すべての処理が問題なく終わるとツール画面が表示されます。
（※下記の画像では、「iframe」を使って同一Webページ内にツール画面を埋め込む形で起動していますが、新規ウィンドウ上で起動するように設定することも可能です。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725519/c43e4713-47c6-1893-c853-716484b815c3.png)
LTIによってSSOが実現されているため、ツール起動時に再度ID・PWを入力してログインする必要はありません。また、今回起動したツールではLTIによってプラットフォームからツールに連携された情報を基に、トップ画面にユーザー名が表示される仕様になっています。[記事① 1章](https://qiita.com/Hiroki_Kobari/items/2419299342367a9f2535)で説明しているように、これらがLTIを活用するメリットの一つになります。

# 参考資料
+ [Learning Tools Interoperability (LTI)® Core Specification](https://www.imsglobal.org/spec/lti/v1p3/)
+ [Learning Tools Interoperability (LTI) Assignment and Grade Services Specification](https://www.imsglobal.org/spec/lti-ags/v2p0/)
+ [Learning Tools Interoperability (LTI)® Deep Linking Specification](https://www.imsglobal.org/spec/lti-dl/v2p0/)
+ [Learning Tools Interoperability (LTI)® Names and Role Provisioning Services](https://www.imsglobal.org/spec/lti-nrps/v2p0)
+ [1EdTech Security Framework](https://www.imsglobal.org/spec/security/v1p1)
+ [OpenID Connect Core 1.0 incorporating errata set 1](https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html)
+ [学習基盤を拡張する国際技術標準 IMS LTI 1.3 第1回～第3回](https://ipsj.ixsq.nii.ac.jp/ej/?action=pages_view_main&active_action=repository_view_main_item_snippet&all=&title=%E5%AD%A6%E7%BF%92%E5%9F%BA%E7%9B%A4%E3%82%92%E6%8B%A1%E5%BC%B5%E3%81%99%E3%82%8B%E5%9B%BD%E9%9A%9B%E6%8A%80%E8%A1%93%E6%A8%99%E6%BA%96%20ims%20lti%201.3&count=20&order=7&pn=1&page_id=13&block_id=8)
+ [1EdTech lti-1-3-php-example-tool](https://github.com/1EdTech/lti-1-3-php-example-tool)
+ [Moodle](https://github.com/moodle/moodle)