LTI integration of the Serlo Editor in edu sharing - serlo/documentation GitHub Wiki

This wiki page provides technical documentation about the integration of the Serlo Editor in the edu-sharing platform. See the repository README for general information about the integration. Edu-sharing is a learning platform which is for example deployed in the LMS of Rheinland-Pfalz. Edu-sharing offers a folder & file structure to organize and to index & search learning content.

Technical perspective of the Serlo Editor integration

Edu-sharing and the Serlo editor interact by following a protocol called LTI. LTI is a standard that allows a learning platform and a learning tool to exchange data. See this short introduction to LTI.

When a user on edu-sharing opens a file using the Serlo editor, edu-sharing will launch the Serlo Editor as an LTI tool. Edu-sharing acts as a LTI platform in this case. Internally, this launch will be handled by the library lti.js.

A different kind of LTI launch happens when the user want to embed content from edu-sharing (for example an image) into the content within the Serlo editor. In that case, the Serlo editor will launch edu-sharing as an LTI tool. This is called the LTI Deep Linking flow. That means, while the Serlo Editor was launched as a LTI Tool first, it now acts as a LTI Platform and launches edu-sharing as a LTI tool. Internally, no library is used and the necessary endpoints are implemented ourselves.

LTI launch messages

LTI specifies a series of messages that need to be exchanged in order to perform a LTI launch. These are not exchanged directly between the platform and tool server. Instead, the browser acts as middleman in the communication. That means, a series of messages will be sent back and forth within a browser tab or iframe using redirects or auto post forms. When this is finished, the browser tab or iframe will contain the requested resource from the tool.

LTI and JWT

Most messages in LTI are going through the browser. In order to verify the authenticity of a message, LTI uses JWT. Using the public key of the sender, the recipient of a message can verify the JWT signature. This ensures that the content originates from a trusted sender and was not tempered with.

lti.js

Lti.js handles the entire launch process of opening the Serlo Editor as a LTI tool.

When the editor is successfully launched, lti.js will create a signed session cookie in the database and create a JWT called ltik. Both contain information about the platform and user id during the launch.

Each following request going to endpoints '/lti' or '/lti/...' needs to supply the session cookie and the ltik for authentication. If authentication fails, the request will not be forwarded to the request handler. See: https://cvmcosta.me/ltijs/#/provider?id=request-authentication

Sequence diagram of Deep Linking LTI flow (Platform: Editor, Tool: edu-sharing)

Before this exchange, the editor was already successfully launched as an LTI tool by edu-sharing and the user is currently inside the editor.

This flow is initiated when a user wants to embed content from edu-sharing within the Serlo Editor.

The implementation follows closely the LTI specification, see: https://www.imsglobal.org/spec/security/v1p0/#openid_connect_launch_flow, see: https://www.imsglobal.org/spec/lti-dl/v2p0

Only Message Names

%%{init: { "sequence": { "wrap": true, "width":500 } } }%%
sequenceDiagram
    participant Edusharing as Edusharing Server
    participant Browser as Serlo Editor in Browser
    participant Editor as Serlo Editor Server

    Note over Browser: Trigger: User clicks button "embed content from edusharing" within plugin. Then, an iframe is created and the following exchange happens within.

    Browser->>Editor: GET /lti/start-edusharing-deeplink-flow
    
    Editor->>Browser: "Login Request" auto form
    
    Browser->>Edusharing: Form is automatically submitted<br/>GET {edusharing}/rest/lti/v13/oidc/login_initiations
    
    Edusharing->>Browser: "Authentication Request" auto form

    Browser->>Editor: Form is automatically submitted<br/>GET /platform/login

    Editor->>Browser: "Authentication Response containing LTI Deep Linking Request" auto form<br/> id_token is sent along with it.

    Browser->>Edusharing: Form is automatically submitted<br/>POST {edusharing}/rest/lti/v13/lti13

    Edusharing->>Editor: GET /platform/keys

    Editor->>Edusharing: Public keys to check it_token signature

    Edusharing->>Browser: Redirect to {edusharing}/components/search

    Note over Browser: Edusharing presents the user with possible resources to embed. Blocks until user made a selection. 

    Browser->>Editor: "LTI Deep Linking Response"<br/>POST /platform/done<br/> JWT is sent along containing nodeId that was selected by user

    Editor->>Edusharing: GET /edu-sharing/rest/lti/v13/jwks

    Edusharing->>Editor: Public keys to check JWT

    Editor->>Browser: HTML containing script that sends repositoryId and nodeId to the parent of the iframe. 

    Note over Browser: Iframe is closed. Editor edu-sharing plugin continues now. 

    Browser->>Editor: GET /lti/get-embed-html

    Editor->>Edusharing: GET /edu-sharing/rest/lti/v13/details/local/{nodeId}<br/> jwt is sent along

    Edusharing->>Editor: GET /platform/keys

    Editor->>Edusharing: Public keys to check jwt signature 

    Edusharing->>Editor: HTML snippet

    Editor->>Browser: HTML snippet

    Note over Browser: Resource that was selected by the user is displayed within the editor plugin frame. 
Loading

Detailed

%%{init: { "sequence": { "wrap": true, "width":500 } } }%%
sequenceDiagram
    participant Edusharing as Edusharing Server
    participant Browser as Serlo Editor in Browser
    participant Editor as Serlo Editor Server

    Note over Browser: Trigger: User clicks button "embed content from edusharing" within plugin. Then, an iframe is created and the following exchange happens within.

    Browser->>Editor: GET /lti/start-edusharing-deeplink-flow<br/>+ltik +{cookies set by lti.js during editor LTI launch}
    
    Note over Editor: Checks: <br/>Are ltik & cookies valid? (within lti.js) <br/>Does LTI custom claim have expected type? <br/>Then: <br/>Creates session in database (deeplinkLoginData) using user, nodeId, dataToken. 
    
    Editor->>Browser: "Login Request" auto form<br/>+iss, +target_link_uri, +login_hint,<br/> +client_id=editor, +lti_deployment_id=2
    
    Browser->>Edusharing: Form is automatically submitted<br/>GET {edusharing}/rest/lti/v13/oidc/login_initiations
    
    Note over Edusharing: Checks: <br/>Is iss & client_id allowed to launch Edusharing as tool? <br/>Is target_link_uri the correct lti launch uri of this tool?
    
    Edusharing->>Browser: "Authentication Request" auto form <br/>+scope=openid, +response_type=id_token, +client_id=editor, <br/>+login_hint, +state, +response_mode=form_post, <br/>+nonce, +prompt=none, +redirect_uri={edusharing}/rest/lti/v13/lti13

    Note over Browser: Cookie for edu-sharing session is set. 

    Browser->>Editor: Form is automatically submitted<br/>GET /platform/login <br/>+{session cookies set by lti.js}

    Note over Editor: Checks: <br/>Is there a valid session (deeplinkLoginData) in the database with id login_hint? <br/>Is this client_id and redirect_uri allowed to launch as a tool on this platform? <br/>Then: <br/>Add nonce to database. <br/>Create and sign id_token

    Editor->>Browser: "Authentication Response containing LTI Deep Linking Request" auto form<br/>+id_token, +state

    Browser->>Edusharing: Form is automatically submitted<br/>POST {edusharing}/rest/lti/v13/lti13 <br/>+{session cookies set by edu-sharing}

    Note over Edusharing: Checks: <br/>Does state match what was sent? <br/>Does nonce value in id_token match what was sent? <br/>Is iss allowed?

    Edusharing->>Editor: GET /platform/keys

    Editor->>Edusharing: Public keys to check it_token signature

    Note over Edusharing: Checks: <br/>Is id_token signature valid? <br/>

    Edusharing->>Browser: Redirect to {edusharing}/components/search

    Note over Browser: Edusharing presents the user with possible resources to embed. Blocks until user made a selection. 

    Browser->>Editor: "LTI Deep Linking Response" containing nodeId that was selected by user to embed<br/>POST /platform/done<br/>+JWT +{session cookies set by lti.js}

    Note over Editor: Checks: <br/>Is nonce in JWT found in database. 

    Editor->>Edusharing: GET /edu-sharing/rest/lti/v13/jwks

    Edusharing->>Editor: Public keys to check jwt

    Note over Editor: Checks: <br/>Is JWT signature valid? 

    Editor->>Browser: HTML containing script that sends repositoryId and nodeId to the parent of the iframe. 

    Note over Browser: Iframe is closed. Editor edu-sharing plugin continues now. 

    Browser->>Editor: GET /lti/get-embed-html<br/>+nodeId, +repositoryId<br/>+Authorization: Bearer {ltik}, +{session cookies set by lti.js}

    Editor->>Edusharing: GET /edu-sharing/rest/lti/v13/details/local/{nodeId}<br/>+displayMode=inline, +jwt

    Edusharing->>Editor: GET /platform/keys

    Editor->>Edusharing: Public keys to check jwt signature 

    Note over Edusharing: Checks: <br/>Is jwt signature valid?

    Edusharing->>Editor: HTML snippet

    Editor->>Browser: HTML snippet

    Note over Browser: Resource that was selected by the user is displayed within the editor plugin frame. 
Loading

Parameters and token in sequence diagram

Parameter

  • iss: Issuer. Who is requesting or who created a JWT.
  • iat: Issued At Time.
  • exp: Expiry time. Token becomes invalid at that time.
  • aud: Audience. Who are the recipients of this token.
  • sub: Subject in LTI. User id.
  • login_hint: Unique identifier created by the serlo editor server referring to a session in the database. Should come back unmodified when receiving an Authentication Request.
  • target_link_uri: Final tool uri for the LTI launch. For edu-sharing: .../edu-sharing/rest/lti/v13/lti13
  • dataToken: Supplied by edu-sharing during the lti launch of the editor. Sent back to edu-sharing in some interactions. Used by edu-sharing to check for correct sender of messages. Does not have to be processed in any way outside of edu-sharing, just send it back when edu-sharing expects it.
  • nodeId: Id of a file in edu-sharing. During the launch of the Serlo Editor the nodeId is the file that was opened in edu-sharing with the editor. Example: 604f62c1-6463-4206-a571-8c57097a54ae. When embedding content from edu-sharing in the editor, the nodeId will be the id of the file that you embed.
  • lti_deployment_id: A unique id to a specific LIT launch.
  • nonce: Value that uniquely identifies an Authentication Request. Coming from the editor server. Needs to be included in the id_token in the response to the Authentication Request. See: https://www.imsglobal.org/spec/security/v1p0/#step-2-authentication-request
  • state: Used to maintain state between request and callback on the edu-sharing server. Can be used to set a cookie for edu-sharing to prevent CSRF attacks. Serlo Editor just sends it back as it is. See: https://openid.net/specs/openid-connect-core-1_0.html

Token

  • ltik: Created by lti.js after successfully launching the editor as a LTI tool. Contains information like: The user who launched the tool. The platform. The context of the launch, meaning the nodeId of the resource on edu-sharing that was opened with the editor. The ltik is a JWT signed with a secret key. Secret key is set in Provider.setup(...), see: https://cvmcosta.me/ltijs/#/provider?id=setting-up-ltijs This token is shared with the browser and used by lti.js to authenticate sessions.

Example content:

{
  "platformUrl": "http://repository.127.0.0.1.nip.io:8100/edu-sharing",
  "clientId": "d439...",
  "deploymentId": "1",
  "platformCode": "ltiaHR...",
  "contextId": "http%3A%2F%2Frepository.127.0.0.1.nip.io%3A8100%2Fedu-sharingd439GQNpgPdPwbG1b72d35f7-28...",
  "user": "admin",
  "s": "d42f...",
  "iat": 1677163358
}
  • id_token: JWT that contains information about user, context, ... under which edu-sharing should be launched as a LTI tool. Also contains a deep linking request within.

Example content:

{
  "iss": "http://localhost:3000/",
  "aud": "editor",
  "sub": "admin",
  "nonce": "225c...",
  "dataToken": "HU80...",
  "https://purl.imsglobal.org/spec/lti/claim/deployment_id": "2",
  "https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiDeepLinkingRequest",
  "https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
  "https://purl.imsglobal.org/spec/lti/claim/roles": [],
  "https://purl.imsglobal.org/spec/lti/claim/context": {
    "id": "6ae2..."
  },
  "https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings": {
    "accept_types": [
      "ltiResourceLink"
    ],
    "accept_presentation_document_targets": [
      "iframe"
    ],
    "accept_multiple": true,
    "auto_create": false,
    "deep_link_return_url": "http://localhost:3000/platform/done",
    "title": "",
    "text": "",
    "data": "63f7..."
  },
  "iat": 1677163372,
  "exp": 1677163387
}
  • JWT: JWT coming from edu-sharing. Later used to authenticate the access to a certain nodeID on edusharing. Sent within the Deep Linking response. Array content_items lists all resources on edu-sharing that should be embedded. In the example, the nodeID "b3a..." should be embedded.

Content example:

{
  "iss": "editor",
  "aud": "http://localhost:3000/",
  "exp": 1677166987,
  "iat": 1677163387,
  "nonce": "225...",
  "azp": "http://localhost:3000/",
  "https://purl.imsglobal.org/spec/lti/claim/deployment_id": "2",
  "https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiDeepLinkingResponse",
  "https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
  "https://purl.imsglobal.org/spec/lti-dl/claim/data": "63f...",
  "https://purl.imsglobal.org/spec/lti-dl/claim/content_items": [
    {
      "custom": {
        "repositoryId": "local",
        "nodeId": "b3a..." // Resource on edu-sharing that was selected by user that will be embedded. 
      },
      "icon": {
        "width": "null",
        "url": "http://repository.127.0.0.1.nip.io:8100/edu-sharing/themes/default/images/common/mime-types/svg/file-image.svg",
        "height": "null"
      },
      "type": "ltiResourceLink",
      "title": "assets.serlo.org/5a8...a.png",
      "url": "http://repository.127.0.0.1.nip.io:8100/edu-sharing/rest/lti/v13/lti13/b3a..."
    }
  ]
}
  • jwt: Created & signed by the editor server and sent along when requesting the html snippet to the resource that should be embedded.
{
  "aud": "editor",
  "https://purl.imsglobal.org/spec/lti/claim/deployment_id": "2",
  "expiresIn": 60,
  "dataToken": "ji4...",
  "https://purl.imsglobal.org/spec/lti/claim/context": {
    "id": "editor"
  },
  "iat": 1677696350,
  "exp": 1677696365
}
⚠️ **GitHub.com Fallback** ⚠️