Skip to content

Add LTI 1.3 features.#2993

Open
drgrice1 wants to merge 2 commits into
openwebwork:WeBWorK-2.21from
drgrice1:lti-1.3-features
Open

Add LTI 1.3 features.#2993
drgrice1 wants to merge 2 commits into
openwebwork:WeBWorK-2.21from
drgrice1:lti-1.3-features

Conversation

@drgrice1

@drgrice1 drgrice1 commented Jun 1, 2026

Copy link
Copy Markdown
Member

First, the lineitems URL that is sent with any resource link (and even for a deep linking request, i.e., a content selection request) is stored in the course settings whenever one of those occur. This lineitems URL is unique for the external tool, and can be used to fetch a list of all resource links that use the external tool. Then when any grade pass back is performed this URL is used to get that list which includes the lineitem URL for each specific resource link. That is the link that is used for grade pass back for the set. However, webwork needs to be able to identify the set it belongs to. Since it does not come from a resource link launch the webwork2 set id is not available. To fix this, when deep linking is used to create a resource link, webwork now sets the resourceId of the lineitem. Note that this is a reserved value for the tool provider, and the LMS (the tool consumer) is not supposed to ever change it, and must send it when the lineitems URL is used. Thus is deep linking is used to create a resource link to a set, then grade pass back will be available immediately for that set without a user ever using the link.

Second, implement LMS roster synchronization. The requires that the names and roles service be enabled for the external tool. The registration URL now includes the
https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly scope which makes this so. Note that tools created manually without the registration URL, this service can be enabled for the tool. However, at least for Canvas, if the tool was created using the registration URL before this scope was added, the only way to add the scope is to have the Canvas admin delete the tool and recreate it again using the registration URL with what is in this pull request. Moodle still allows editing of the tool created via the registration URL, so this can be added later. In any case, if the names and roles service is enabled, than any launch request (again including a deep linking request) includes the namesrolesservice URL. That can be used to request the list of users in the LMS course. There is now a new tab that will appear in the accounts manager if LTI 1.3 is enabled for a course, the preferred_source_of_username is set, and this namesrolesservice URL has been obtained (by some user using content selection or a resource link). When this tab is available and its action used, the list of users in the LMS course will be obtained, and users will be created in webwork that do not already exist, users will be updated that do exist (if LMSManageUserData is true), and users not in the LMS course will have their status changed to dropped. Note that the data sent when using the namesrolesservice URL is not the same as that sent in a launch request. So there are some new options that control what is used for the username and student_id. See the options and documentation in conf/authen_LTI_1_3.conf.dist. Also note that this will never add, update, or modify in any way any users that have a role that is above the LTIAccountCreationCutoff.

Note that when users are created or updated via the tab in the accounts manager and the namesrolesservice URL, the LMS user_id (or sub) is included in the data received and saved to the lis_source_did column for the user. So this means that for links created via deep linking with this pull request and users added or updated in this way, grade pass back is available immediately, even if the user never logs in to webwork via the LMS.

Also fix plurality of some texts for some other similar action results in the accounts manager.

Unfortunately the message on the assignments page stating "You must access this assignment from the LMS before you can start." is still shown even if an instructor uses deep linking to create links in the LMS. The problem is that although grade passabck will work for those sets, the lineitem URL is still not saved in the database for all of those sets. The lineitem is not obtained until grade pass back occurs or as before, a user uses the link from the LMS. So those that create all links via deep linking will most likely want to set $LTI{v1p3}{ignoreMissingSourcedID} so that users are not shown that message, and can access the sets. As such, I made that option one that can be made available for instructors to set on the LTI tab in the course configuration.

Third, implement set date synchronization with the LMS. Set dates can be synchronized to or from the LMS. There is a new tab for this in the sets manager that is shown if LTI 1.3 with homework grade pass back is enabled and the general lineitems URL is available. Also there is a new LTI 1.3 option $LTI{v1p3}{autoSyncSetDatesToLMS}. If this is true, then anytime that dates are changed on the sets manager or problem set detail pages, a job to synchronize dates to the LMS will automatically be queued for the sets with dates changed.

Note that only the open and due (or close) dates have an equivalent in the LTI specification, namely the start and end dates. So when synchronizing from an LMS, the reduced scoring date and answer date are adjusted if needed to make them fit into the dates received from the LMS. Of course, when sending dates, only the open and due date are sent.

It is also important to note that date synchronization is not supported by all LMSs. Canvas does, but Moodle does not. As usual, I don't know what D2L or Blackboard support.

One related small change is that set dates are now also set when grade pass back mode is homework and a link is created to a set via deep linking.

Another small change to the deep linking response is that window => { targetName => '_blank' } is set. This makes Canvas open the links in a new window instead of being embedded in the page. This does not work for Moodle, and as far as I can tell, there is no way to make this happen from the deep linking response. Fortunately for Moodle users, the external tool can be set so that all links created that use the external tool open in a new window. Canvas on the other hand does not have that. So without this change, Canvas users that create links have to go edit each link and change it to open in a new window.

@drgrice1

drgrice1 commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

This also now includes a change to the dynamic registration method to make it work for D2L. D2L does not send the registration_token. According to the specification it is optional, but Moodle and Canvas both send it. If it is sent then it must be used for the Authorization Bearer header. Otherwise that header is omitted.

@drgrice1 drgrice1 force-pushed the lti-1.3-features branch 2 times, most recently from 684a620 to dfc665c Compare June 9, 2026 22:15
@drgrice1 drgrice1 force-pushed the lti-1.3-features branch 3 times, most recently from fb015d4 to 0c40817 Compare June 17, 2026 01:02
@Alex-Jordan

Copy link
Copy Markdown
Contributor

I have a meeting this week with both a D2L admin and a web team admin who can adjust the https communication from my test server for LTI connections with our D2L test server. So I hope to get this all tested (all the way through #2995) by the end of this coming week.

@drgrice1 drgrice1 force-pushed the lti-1.3-features branch 3 times, most recently from 7fe0f35 to de6e183 Compare June 24, 2026 21:39
@Alex-Jordan

Copy link
Copy Markdown
Contributor

I'll pick up testing with D2L starting roughly July 7.

@drgrice1 drgrice1 force-pushed the lti-1.3-features branch 6 times, most recently from ed2497f to a482274 Compare July 9, 2026 21:51
@drgrice1

drgrice1 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

This also now includes some more changes to the dynamic registration URL to make things work for D2L. Thanks to @Alex-Jordan and @dlglin for helping to figure out what was needed there.

@Alex-Jordan

Copy link
Copy Markdown
Contributor

I'm going to wait until @dlglin confirms the new change works before I ask my D2L admin to try dynamic registration again. Please ping me once you have confirmed.

Looking at the code, how does $lmsConfiguration->{issuer} identify that Canvas is the LMS?

@drgrice1 drgrice1 force-pushed the lti-1.3-features branch from a482274 to aa60ad2 Compare July 9, 2026 22:23
@drgrice1

drgrice1 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

There was a copy/paste mistake in that. I meant the second argument to index to be the string '.instructure.com', but pasted the wrong buffer, and got the same as the first argument.

In any case, if the issuer contains the string .instructure.com, then it is Canvas. That is the same test that the IMathAS code uses.

@Alex-Jordan

Copy link
Copy Markdown
Contributor

Have you tested that things are still working now with Canvas and Moodle?

@drgrice1

drgrice1 commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Yes, I did.

@dlglin

dlglin commented Jul 10, 2026

Copy link
Copy Markdown
Member

The dynamic registration works for me now on D2L. It creates the registration, deployment and content selection link if you leave the "Configure deployment" box checked. There are still a couple of steps on the D2L side that need to be done (which are standard for this sort of thing):

  • The registration will need to be enabled
  • On my server the tool is only made available to the top level org unit, so I had to make it available to the relevant courses. To make it available to all courses:
    • Edit the deployment.
    • Click the "Add Org Units" button
    • Choose "Organization" from the dropdown, and check the box for your organization (If you only want to make it available to a subset of courses on your D2L instance, then choose the appropriate Org Unit).
    • Check "All descendants"
    • Click "Add".
  • I also recommend checking the "Open as External Resource" box for the deployment. This means that all new links created will open in a new window by default.

I haven't tested roster sync yet.

@dlglin

dlglin commented Jul 10, 2026

Copy link
Copy Markdown
Member

Roster sync works for me with a couple of notes:

The default settings in D2L only sync users who have already accessed the tool. In order to sync all D2L users you need to check the "Classlist including users not known to this deployment" box under Security Settings for the deployment in D2L. @Alex-Jordan this explains why only your user was being synced from D2L to WW.

The Names and Roles service for D2L only sends five parameters:

  • Full Name
  • Email
  • LTI UserId
  • LIS_Person_SourcedId
  • IMS Context Roles

This means that some information can't be pre-populated before a student first accesses the tool. In particular the section information, student ID and D2L username can be sent during a login request, but are not sent during the roster sync.

@Alex-Jordan

Copy link
Copy Markdown
Contributor

The Names and Roles service for D2L only sends five parameters:

Does this meant that roster syncing will fail if the WW username is not derived from one of those five? Most places use email prefixes, as far as I know. So that is good. I know of a few that use the LMS username (a more anonymized ID). And it seems reasonable that some place might use student ID.

@dlglin

dlglin commented Jul 10, 2026

Copy link
Copy Markdown
Member

Here's what the service sends on my instance of D2L (with values removed):

  "family_name" => "xxx",
  "given_name" => "xxx",
  "name" => "xxx",
  "roles" => [
    "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"
  ],
  "status" => "Active",
  "user_id" => "xxx"

In our case the user_id is an internal D2L identifier.

As far as I can tell you would have to set $LTI{v1p3}{namesroles_service_preferred_source_of_username} to one of those things. If that doesn't give the same value as $LTI{v1p3}{preferred_source_of_username} does during authentication then you will end up with duplicate accounts.

I assume that user_id would be unique for every user in your D2L instance. If you're only using LTI for authentication then this should be fine. Where it gets complicated is having accounts that can authenticate via both LTI and LDAP. In that case the username provided by the names and roles service would need to match their LDAP username. If every D2L user has their institutional email address as their email in D2L, and that email prefix is always the same as their username, then this is fine. Otherwise it could be a problem.

@drgrice1 drgrice1 force-pushed the lti-1.3-features branch 3 times, most recently from e8b4f3f to 5f2702b Compare July 10, 2026 21:42
@drgrice1

Copy link
Copy Markdown
Member Author

I did some more work on the Mojo::UserAgent usage. Basically, every time an asynchronous request is utilized, exceptions should be caught and handled.

@Alex-Jordan

Copy link
Copy Markdown
Contributor

Thanks @dlglin, I've asked my D2L admin to use dynamic registration again on our test server. Once that is done, I'll run through testing these PRs.

@drgrice1

drgrice1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

As to the issue with the roster sync user id issue, unfortunately different data is sent when LTI authentication occurs and when the names roles service URL.

If you want a completely guaranteed source of the user id that will certainly be the same for both then use the LMS user id (although it is the sub when LTI authentication occurs and the user_id for roster sync). That is always sent and is the same for both, and is unique within the LMS. Of course, that is not a very friendly user_id (usually an auto increment database value or some sort of UUID). The next best is the email address. That is almost always sent (it is not sent, for example, for the Canvas test student, but that probably doesn't matter). After that you are going to be hard pressed to find something that will work for both.

As to the student id, it is going to be even less likely that you will find something that works for both. It may depend on your institution. I believe that there are custom parameters that will be sent in some cases.

So if you are not using anything other than email (or the LMS user id), then set $debug_lti_parameters = 1; and use both methods and compare the data. When LTI authentication occurs the entire JWT is dumped, and when roster sync occurs, each user's data is dumped.

drgrice1 added 2 commits July 11, 2026 21:14
First, the `lineitems` URL that is sent with any resource link (and even
for a deep linking request, i.e., a content selection request) is stored
in the course settings whenever one of those occur.  This `lineitems`
URL is unique for the external tool, and can be used to fetch a list of
all resource links that use the external tool. Then when any grade pass
back is performed this URL is used to get that list which includes the
`lineitem` URL for each specific resource link. That is the link that is
used for grade pass back for the set.  However, webwork needs to be able
to identify the set it belongs to.  Since it does not come from a
resource link launch the webwork2 set id is not available.  To fix this,
when deep linking is used to create a resource link, webwork now sets
the `resourceId` of the lineitem.  Note that this is a reserved value
for the tool provider, and the LMS (the tool consumer) is not supposed
to ever change it, and must send it when the `lineitems` URL is used.
Thus is deep linking is used to create a resource link to a set, then
grade pass back will be available immediately for that set without a
user ever using the link.

Second, implement LMS roster synchronization.  The requires that the
names and roles service be enabled for the external tool.  The
registration URL now includes the
`https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly`
scope which makes this so. Note that tools created manually without the
registration URL, this service can be enabled for the tool.  However, at
least for Canvas, if the tool was created using the registration URL
before this scope was added, the only way to add the scope is to have
the Canvas admin delete the tool and recreate it again using the
registration URL with what is in this pull request. Moodle still allows
editing of the tool created via the registration URL, so this can be
added later. In any case, if the names and roles service is enabled,
than any launch request (again including a deep linking request)
includes the `namesrolesservice` URL. That can be used to request the
list of users in the LMS course.  There is now a new tab that will
appear in the accounts manager if LTI 1.3 is enabled for a course, the
`preferred_source_of_username` is set, and this `namesrolesservice` URL
has been obtained (by some user using content selection or a resource
link).  When this tab is available and its action used, the list of
users in the LMS course will be obtained, and users will be created in
webwork that do not already exist, users will be updated that do exist
(if `LMSManageUserData` is true), and users not in the LMS course will
have their status changed to dropped.  Note that the data sent when
using the `namesrolesservice` URL is not the same as that sent in a
launch request.  So there are some new options that control what is used
for the username and student_id.  See the options and documentation in
`conf/authen_LTI_1_3.conf.dist`. Also note that this will never add,
update, or modify in any way any users that have a role that is above
the `LTIAccountCreationCutoff`.

Note that when users are created or updated via the tab in the accounts
manager and the `namesrolesservice` URL, the LMS `user_id` (or `sub`) is
included in the data received and saved to the `lis_source_did` column
for the user.  So this means that for links created via deep linking
with this pull request and users added or updated in this way, grade
pass back is available immediately, even if the user never logs in to
webwork via the LMS.

Also fix plurality of some texts for some other similar action results
in the accounts manager.

Unfortunately the message on the assignments page stating "You must
access this assignment from the LMS before you can start." is still
shown even if an instructor uses deep linking to create links in the
LMS.  The problem is that although grade passabck will work for those
sets, the lineitem URL is still not saved in the database for all of
those sets.  The lineitem is not obtained until grade pass back occurs
or as before, a user uses the link from the LMS. So those that create
all links via deep linking will most likely want to set
`$LTI{v1p3}{ignoreMissingSourcedID}` so that users are not shown that
message, and can access the sets. As such, I made that option one that
can be made available for instructors to set on the LTI tab in the
course configuration.

Third, implement set date synchronization with the LMS. Set dates can be
synchronized to or from the LMS. There is a new tab for this in the sets
manager that is shown if LTI 1.3 with homework grade pass back is
enabled and the general lineitems URL is available. Also there is a new
LTI 1.3 option `$LTI{v1p3}{autoSyncSetDatesToLMS}`. If this is true,
then anytime that dates are changed on the sets manager or problem set
detail pages, a job to synchronize dates to the LMS will automatically
be queued for the sets with dates changed.

Note that only the open and due (or close) dates have an equivalent in
the LTI specification, namely the start and end dates. So when
synchronizing from an LMS, the reduced scoring date and answer date are
adjusted if needed to make them fit into the dates received from the
LMS. Of course, when sending dates, only the open and due date are sent.

It is also important to note that date synchronization is not supported
by all LMSs.  Canvas does, but Moodle does not.  As usual, I don't know
what D2L or Blackboard support.

One related small change is that set dates are now also set when grade
pass back mode is homework and a link is created to a set via deep
linking.

Another small change to the deep linking response is that
`window => { targetName => '_blank' }` is set. This makes Canvas open
the links in a new window instead of being embedded in the page. This
does not work for Moodle, and as far as I can tell, there is no way to
make this happen from the deep linking response.  Fortunately for Moodle
users, the external tool can be set so that all links created that use
the external tool open in a new window.  Canvas on the other hand does
not have that.  So without this change, Canvas users that create links
have to go edit each link and change it to open in a new window.
…from the LMS optional.

According to the specification it is optional, and apparently D2L does
not send it.  Both Moodle and Canvas do.

Also make sure that the `placements` message parameter is only sent for
Canvas. Although Moodle ignores this parameter, D2L rejects the message
if the parameter is included.

Also add a `description` to the lti tool configuration, and a label to
the deep linking message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants