diff --git a/README.rst b/README.rst
index cc9a2d3396da09ade14de1b25a7d076891053cbe..99248138f03d156047921e27cbc8d417907e9655 100644
--- a/README.rst
+++ b/README.rst
@@ -367,6 +367,11 @@ Changelog
 
 Please See the [releases tab](https://github.com/edx/xblock-lti-consumer/releases) for the complete changelog.
 
+3.3.0 - 2022-01-20
+-------------------
+
+* Added support for specifying LTI 1.3 JWK URLs.
+
 3.2.0 - 2022-01-18
 -------------------
 
diff --git a/lti_consumer/__init__.py b/lti_consumer/__init__.py
index 0ddc23dac6db7e92c29851b3cf718e24dfd14ef5..06a949dcd7b850dab33052b9fd18d7bcfb814701 100644
--- a/lti_consumer/__init__.py
+++ b/lti_consumer/__init__.py
@@ -4,4 +4,4 @@ Runtime will load the XBlock class from here.
 from .lti_xblock import LtiConsumerXBlock
 from .apps import LTIConsumerApp
 
-__version__ = '3.2.0'
+__version__ = '3.3.0'
diff --git a/lti_consumer/lti_1p3/key_handlers.py b/lti_consumer/lti_1p3/key_handlers.py
index e49193aedfa22244ee5053dfc5c4cbbab206d5b5..6fc689dd0d48d29b3b3ceba6abe044ab230d6493 100644
--- a/lti_consumer/lti_1p3/key_handlers.py
+++ b/lti_consumer/lti_1p3/key_handlers.py
@@ -68,8 +68,15 @@ class ToolKeyHandler:
         keyset = []
 
         if self.keyset_url:
-            # TODO: Improve support for keyset handling, handle errors.
-            keyset.extend(load_jwks_from_url(self.keyset_url))
+            try:
+                keys = load_jwks_from_url(self.keyset_url)
+            except Exception as err:
+                # Broad Exception is required here because jwkest raises
+                # an Exception object explicitly.
+                # Beware that many different scenarios are being handled
+                # as an invalid key when the JWK loading fails.
+                raise exceptions.NoSuitableKeys() from err
+            keyset.extend(keys)
 
         if self.public_key and kid:
             # Fill in key id of stored key.
diff --git a/lti_consumer/lti_xblock.py b/lti_consumer/lti_xblock.py
index 4ca545f949eb4022db038b450ac27e65898a03d3..8970c2c44ca63baa8be83166061cebb816a34095 100644
--- a/lti_consumer/lti_xblock.py
+++ b/lti_consumer/lti_xblock.py
@@ -283,6 +283,33 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
             "prior to doing the launch request."
         ),
     )
+
+    lti_1p3_tool_key_mode = String(
+        display_name=_("Tool Public Key Mode"),
+        scope=Scope.settings,
+        values=[
+            {"display_name": "Public Key", "value": "public_key"},
+            {"display_name": "Keyset URL", "value": "keyset_url"},
+        ],
+        default="public_key",
+        help=_(
+            "Select how the tool's public key information will be specified."
+        ),
+    )
+    lti_1p3_tool_keyset_url = String(
+        display_name=_("Tool Keyset URL"),
+        default='',
+        scope=Scope.settings,
+        help=_(
+            "Enter the LTI 1.3 Tool's JWK keysets URL."
+            "<br />This link should retrieve a JSON file containing"
+            " public keys and signature algorithm information, so"
+            " that the LMS can check if the messages and launch"
+            " requests received have the signature from the tool."
+            "<br /><b>This is not required when doing LTI 1.3 Launches"
+            " without LTI Advantage nor Basic Outcomes requests.</b>"
+        ),
+    )
     lti_1p3_tool_public_key = String(
         display_name=_("Tool Public Key"),
         multiline_editor=True,
@@ -520,7 +547,9 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
     editable_field_names = (
         'display_name', 'description',
         # LTI 1.3 variables
-        'lti_version', 'lti_1p3_launch_url', 'lti_1p3_oidc_url', 'lti_1p3_tool_public_key', 'lti_1p3_enable_nrps',
+        'lti_version', 'lti_1p3_launch_url', 'lti_1p3_oidc_url',
+        'lti_1p3_tool_key_mode', 'lti_1p3_tool_keyset_url', 'lti_1p3_tool_public_key',
+        'lti_1p3_enable_nrps',
         # LTI Advantage variables
         'lti_advantage_deep_linking_enabled', 'lti_advantage_deep_linking_launch_url',
         'lti_advantage_ags_mode',
@@ -574,6 +603,12 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
                 _("Custom Parameters must be a list")
             )))
 
+        # keyset URL and public key are mutually exclusive
+        if data.lti_1p3_tool_key_mode == 'keyset_url':
+            data.lti_1p3_tool_public_key = ''
+        elif data.lti_1p3_tool_key_mode == 'public_key':
+            data.lti_1p3_tool_keyset_url = ''
+
     def get_settings(self):
         """
         Get the XBlock settings bucket via the SettingsService.
diff --git a/lti_consumer/models.py b/lti_consumer/models.py
index a659c929e6bd5cfd5b04583126bd7fc316e89f39..33576a84849b3ca642144c93acbf514e84997624 100644
--- a/lti_consumer/models.py
+++ b/lti_consumer/models.py
@@ -266,7 +266,7 @@ class LtiConfiguration(models.Model):
                 rsa_key_id=self.lti_1p3_private_key_id,
                 # LTI 1.3 Tool key/keyset url
                 tool_key=self.block.lti_1p3_tool_public_key,
-                tool_keyset_url=None,
+                tool_keyset_url=self.block.lti_1p3_tool_keyset_url,
             )
 
             # Check if enabled and setup LTI-AGS
diff --git a/lti_consumer/static/js/xblock_studio_view.js b/lti_consumer/static/js/xblock_studio_view.js
index 0d1792a8aefd146ad7bde5988493f8184bd2055b..aa78c4c54cf8755393118d55c737d8322bbec122 100644
--- a/lti_consumer/static/js/xblock_studio_view.js
+++ b/lti_consumer/static/js/xblock_studio_view.js
@@ -14,6 +14,8 @@ function LtiConsumerXBlockInitStudio(runtime, element) {
     const lti1P3FieldList = [
         "lti_1p3_launch_url",
         "lti_1p3_oidc_url",
+        "lti_1p3_tool_key_mode",
+        "lti_1p3_tool_keyset_url",
         "lti_1p3_tool_public_key",
         "lti_advantage_ags_mode",
         "lti_advantage_deep_linking_enabled",
@@ -72,12 +74,37 @@ function LtiConsumerXBlockInitStudio(runtime, element) {
         });
     }
 
+    /**
+     * Only display the field appropriate for the selected key mode.
+     */
+    function toggleLtiToolKeyMode() {
+        const ltiKeyModeField = $(element).find('#xb-field-edit-lti_1p3_tool_key_mode');
+
+        // find the field containers
+        const ltiKeysetUrlField = $(element).find('[data-field-name=lti_1p3_tool_keyset_url]');
+        const ltiPublicKeyField = $(element).find('[data-field-name=lti_1p3_tool_public_key]');
+
+        const selectedKeyMode = ltiKeyModeField.children("option:selected").val();
+        if (selectedKeyMode === 'public_key') {
+            ltiKeysetUrlField.hide();
+            ltiPublicKeyField.show();
+        } else if (selectedKeyMode === 'keyset_url') {
+            ltiPublicKeyField.hide();
+            ltiKeysetUrlField.show();
+        }
+    }
+
     // Call once component is instanced to hide fields
     toggleLtiFields();
+    toggleLtiToolKeyMode();
 
     // Bind to onChange method of lti_version selector
     $(element).find('#xb-field-edit-lti_version').bind('change', function() {
         toggleLtiFields();
-     });
+    });
 
+    // Bind to onChange method of lti_1p3_tool_key_mode selector
+    $(element).find('#xb-field-edit-lti_1p3_tool_key_mode').bind('change', function() {
+        toggleLtiToolKeyMode();
+    });
 }
diff --git a/lti_consumer/tests/unit/test_lti_xblock.py b/lti_consumer/tests/unit/test_lti_xblock.py
index d9bc4bf93e5b1f08c073e0388832586707fa4d90..1e1a7a758b8c062dbbd6a18d1375151778616845 100644
--- a/lti_consumer/tests/unit/test_lti_xblock.py
+++ b/lti_consumer/tests/unit/test_lti_xblock.py
@@ -4,7 +4,6 @@ Unit tests for LtiConsumerXBlock
 
 import json
 import logging
-import urllib.parse
 from datetime import timedelta
 from unittest.mock import Mock, NonCallableMock, PropertyMock, patch
 
@@ -13,14 +12,14 @@ from Cryptodome.PublicKey import RSA
 from django.conf import settings as dj_settings
 from django.test.testcases import TestCase
 from django.utils import timezone
-from jwkest.jwk import RSAKey
+from jwkest.jwk import RSAKey, KEYS
 
 from lti_consumer.api import get_lti_1p3_launch_info
 from lti_consumer.exceptions import LtiError
 from lti_consumer.lti_1p3.tests.utils import create_jwt
 from lti_consumer.lti_xblock import LtiConsumerXBlock, parse_handler_suffix
 from lti_consumer.tests.unit import test_utils
-from lti_consumer.tests.unit.test_utils import FAKE_USER_ID, make_request, make_xblock
+from lti_consumer.tests.unit.test_utils import FAKE_USER_ID, make_jwt_request, make_request, make_xblock
 from lti_consumer.utils import resolve_custom_parameter_template
 
 HTML_PROBLEM_PROGRESS = '<div class="problem-progress">'
@@ -452,6 +451,8 @@ class TestEditableFields(TestLtiConsumerXBlock):
                     'lti_version',
                     'lti_1p3_launch_url',
                     'lti_1p3_oidc_url',
+                    'lti_1p3_tool_key_mode',
+                    'lti_1p3_tool_keyset_url',
                     'lti_1p3_tool_public_key',
                     'lti_advantage_deep_linking_enabled',
                     'lti_advantage_deep_linking_launch_url',
@@ -1484,17 +1485,7 @@ class TestLti1p3AccessTokenEndpoint(TestLtiConsumerXBlock):
         """
         Test request with invalid JWT.
         """
-        request = make_request(
-            urllib.parse.urlencode({
-                "grant_type": "client_credentials",
-                "client_assertion_type": "something",
-                "client_assertion": "invalid-jwt",
-                "scope": "",
-            }),
-            'POST'
-        )
-        request.content_type = 'application/x-www-form-urlencoded'
-
+        request = make_jwt_request("invalid-jwt")
         response = self.xblock.lti_1p3_access_token(request)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.json_body, {'error': 'invalid_grant'})
@@ -1503,15 +1494,7 @@ class TestLti1p3AccessTokenEndpoint(TestLtiConsumerXBlock):
         """
         Test request with invalid grant.
         """
-        request = make_request(
-            urllib.parse.urlencode({
-                "grant_type": "password",
-                "client_assertion_type": "something",
-                "client_assertion": "invalit-jwt",
-                "scope": "",
-            }),
-            'POST'
-        )
+        request = make_jwt_request("invalid-jwt", grant_type="password")
         request.content_type = 'application/x-www-form-urlencoded'
 
         response = self.xblock.lti_1p3_access_token(request)
@@ -1526,17 +1509,7 @@ class TestLti1p3AccessTokenEndpoint(TestLtiConsumerXBlock):
         self.xblock.save()
 
         jwt = create_jwt(self.key, {})
-        request = make_request(
-            urllib.parse.urlencode({
-                "grant_type": "client_credentials",
-                "client_assertion_type": "something",
-                "client_assertion": jwt,
-                "scope": "",
-            }),
-            'POST'
-        )
-        request.content_type = 'application/x-www-form-urlencoded'
-
+        request = make_jwt_request(jwt)
         response = self.xblock.lti_1p3_access_token(request)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.json_body, {'error': 'invalid_client'})
@@ -1546,17 +1519,7 @@ class TestLti1p3AccessTokenEndpoint(TestLtiConsumerXBlock):
         Test request with valid JWT.
         """
         jwt = create_jwt(self.key, {})
-        request = make_request(
-            urllib.parse.urlencode({
-                "grant_type": "client_credentials",
-                "client_assertion_type": "something",
-                "client_assertion": jwt,
-                "scope": "",
-            }),
-            'POST'
-        )
-        request.content_type = 'application/x-www-form-urlencoded'
-
+        request = make_jwt_request(jwt)
         response = self.xblock.lti_1p3_access_token(request)
         self.assertEqual(response.status_code, 200)
 
@@ -1633,3 +1596,87 @@ class TestDynamicCustomParametersResolver(TestLtiConsumerXBlock):
         self.assertEqual(resolved_value, custom_parameter_template_value)
         assert mock_log.error.called
         mock_import_module.asser_not_called()
+
+
+class TestLti1p3AccessTokenJWK(TestCase):
+    """
+    Unit tests for LtiConsumerXBlock Access Token endpoint when using a
+    LTI 1.3 setup with JWK authentication.
+    """
+    def setUp(self):
+        super().setUp()
+        self.xblock = make_xblock('lti_consumer', LtiConsumerXBlock, {
+            'lti_version': 'lti_1p3',
+            'lti_1p3_launch_url': 'http://tool.example/launch',
+            'lti_1p3_oidc_url': 'http://tool.example/oidc',
+            'lti_1p3_tool_keyset_url': "http://tool.example/keyset",
+        })
+        self.xblock.location = 'block-v1:course+test+2020+type@problem+block@test'
+        self.xblock.save()
+
+        self.key = RSAKey(key=RSA.generate(2048), kid="1")
+
+        jwt = create_jwt(self.key, {})
+        self.request = make_jwt_request(jwt)
+
+    def make_keyset(self, keys):
+        """
+        Builds a keyset object with the given keys.
+        """
+        jwks = KEYS()
+        jwks._keys = keys  # pylint: disable=protected-access
+        return jwks
+
+    @patch("lti_consumer.lti_1p3.key_handlers.load_jwks_from_url")
+    def test_access_token_using_keyset_url(self, load_jwks_from_url):
+        """
+        Test request using the provider's keyset URL instead of a public key.
+        """
+        load_jwks_from_url.return_value = self.make_keyset([self.key])
+        response = self.xblock.lti_1p3_access_token(self.request)
+        load_jwks_from_url.assert_called_once_with("http://tool.example/keyset")
+        self.assertEqual(response.status_code, 200)
+
+    @patch("lti_consumer.lti_1p3.key_handlers.load_jwks_from_url")
+    def test_access_token_using_keyset_url_with_empty_keys(self, load_jwks_from_url):
+        """
+        Test request where the provider's keyset URL returns an empty list of keys.
+        """
+        load_jwks_from_url.return_value = self.make_keyset([])
+        response = self.xblock.lti_1p3_access_token(self.request)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json_body, {"error": "invalid_client"})
+
+    @patch("lti_consumer.lti_1p3.key_handlers.load_jwks_from_url")
+    def test_access_token_using_keyset_url_with_wrong_keys(self, load_jwks_from_url):
+        """
+        Test request where the provider's keyset URL returns wrong keys.
+        """
+        key = RSAKey(key=RSA.generate(2048), kid="2")
+        load_jwks_from_url.return_value = self.make_keyset([key])
+        response = self.xblock.lti_1p3_access_token(self.request)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json_body, {"error": "invalid_client"})
+
+    @patch("jwkest.jwk.request")
+    def test_access_token_using_keyset_url_that_fails(self, request):
+        """
+        Test request where the provider's keyset URL request fails.
+        """
+        request.side_effect = Exception("request fails")
+        response = self.xblock.lti_1p3_access_token(self.request)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json_body, {'error': 'invalid_client'})
+
+    @patch("jwkest.jwk.request")
+    def test_access_token_using_keyset_url_with_invalid_contents(self, request):
+        """
+        Test request where the provider's keyset URL doesn't return valid JSON.
+        """
+        response_mock = Mock()
+        response_mock.status_code = 200
+        response_mock.text = b'this is not a valid json'
+        request.return_value = response_mock
+        response = self.xblock.lti_1p3_access_token(self.request)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json_body, {'error': 'invalid_client'})
diff --git a/lti_consumer/tests/unit/test_utils.py b/lti_consumer/tests/unit/test_utils.py
index f45158759c3057a10aa637c5d68475e45078a384..214db164fe6133d2e1408dd370d105823354ed1c 100644
--- a/lti_consumer/tests/unit/test_utils.py
+++ b/lti_consumer/tests/unit/test_utils.py
@@ -3,6 +3,7 @@ Utility functions used within unit tests
 """
 
 from unittest.mock import Mock
+import urllib
 from webob import Request
 from workbench.runtime import WorkbenchRuntime
 from xblock.fields import ScopeIds
@@ -53,6 +54,21 @@ def make_request(body, method='POST'):
     return request
 
 
+def make_jwt_request(token, **overrides):
+    """
+    Builds a Request with a JWT body.
+    """
+    body = {
+        "grant_type": "client_credentials",
+        "client_assertion_type": "something",
+        "client_assertion": token,
+        "scope": "",
+    }
+    request = make_request(urllib.parse.urlencode({**body, **overrides}), 'POST')
+    request.content_type = 'application/x-www-form-urlencoded'
+    return request
+
+
 def dummy_processor(_xblock):
     """
     A dummy LTI parameter processor.
diff --git a/lti_consumer/translations/en/LC_MESSAGES/text.po b/lti_consumer/translations/en/LC_MESSAGES/text.po
index 2df260212b47f0e5b7a9b5a87838f89bb27c84a6..a4b437a62a3b9e96d661dd20228e836e87a2dbd9 100644
--- a/lti_consumer/translations/en/LC_MESSAGES/text.po
+++ b/lti_consumer/translations/en/LC_MESSAGES/text.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-06-07 15:54-0300\n"
+"POT-Creation-Date: 2022-01-18 07:25-0300\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -38,71 +38,92 @@ msgstr ""
 msgid "Invalid token signature."
 msgstr ""
 
-#: lti_xblock.py:131
+#: lti_xblock.py:126
 msgid "No valid user id found in endpoint URL"
 msgstr ""
 
-#: lti_xblock.py:237
+#: lti_xblock.py:232
 msgid "Display Name"
 msgstr ""
 
-#: lti_xblock.py:239
+#: lti_xblock.py:234
 msgid ""
 "Enter the name that students see for this component. Analytics reports may "
 "also use the display name to identify this component."
 msgstr ""
 
-#: lti_xblock.py:243
+#: lti_xblock.py:238
 msgid "LTI Consumer"
 msgstr ""
 
-#: lti_xblock.py:246
+#: lti_xblock.py:241
 msgid "LTI Application Information"
 msgstr ""
 
-#: lti_xblock.py:248
+#: lti_xblock.py:243
 msgid ""
 "Enter a description of the third party application. If requesting username "
 "and/or email, use this text box to inform users why their username and/or "
 "email will be forwarded to a third party application."
 msgstr ""
 
-#: lti_xblock.py:258
+#: lti_xblock.py:253
 msgid "LTI Version"
 msgstr ""
 
-#: lti_xblock.py:266
+#: lti_xblock.py:261
 msgid ""
 "Select the LTI version that your tool supports.<br />The XBlock LTI Consumer "
 "fully supports LTI 1.1.1, LTI 1.3 and LTI Advantage features."
 msgstr ""
 
-#: lti_xblock.py:272
+#: lti_xblock.py:267
 msgid "Tool Launch URL"
 msgstr ""
 
-#: lti_xblock.py:276
+#: lti_xblock.py:271
 msgid ""
 "Enter the LTI 1.3 Tool Launch URL. <br />This is the URL the LMS will use to "
 "launch the LTI Tool."
 msgstr ""
 
-#: lti_xblock.py:281
+#: lti_xblock.py:276
 msgid "Tool Initiate Login URL"
 msgstr ""
 
-#: lti_xblock.py:285
+#: lti_xblock.py:280
 msgid ""
 "Enter the LTI 1.3 Tool OIDC Authorization url (can also be called login or "
 "login initiation URL).<br />This is the URL the LMS will use to start a LTI "
 "authorization prior to doing the launch request."
 msgstr ""
 
-#: lti_xblock.py:291
+#: lti_xblock.py:287
+msgid "Tool Public Key Mode"
+msgstr ""
+
+#: lti_xblock.py:295
+msgid "Select how the tool's public key information will be specified."
+msgstr ""
+
+#: lti_xblock.py:299
+msgid "Tool Keyset URL"
+msgstr ""
+
+#: lti_xblock.py:303
+msgid ""
+"Enter the LTI 1.3 Tool's JWK keysets URL.<br />This link should retrieve a "
+"JSON file containing public keys and signature algorithm information, so "
+"that the LMS can check if the messages and launch requests received have the "
+"signature from the tool.<br /><b>This is not required when doing LTI 1.3 "
+"Launches without LTI Advantage nor Basic Outcomes requests.</b>"
+msgstr ""
+
+#: lti_xblock.py:313
 msgid "Tool Public Key"
 msgstr ""
 
-#: lti_xblock.py:296
+#: lti_xblock.py:318
 msgid ""
 "Enter the LTI 1.3 Tool's public key.<br />This is a string that starts with "
 "'-----BEGIN PUBLIC KEY-----' and is required so that the LMS can check if "
@@ -111,61 +132,61 @@ msgid ""
 "Advantage nor Basic Outcomes requests.</b>"
 msgstr ""
 
-#: lti_xblock.py:306
+#: lti_xblock.py:328
 msgid "Enable LTI NRPS"
 msgstr ""
 
-#: lti_xblock.py:307
+#: lti_xblock.py:329
 msgid "Enable LTI Names and Role Provisioning Services."
 msgstr ""
 
-#: lti_xblock.py:314
+#: lti_xblock.py:336
 msgid "LTI 1.3 Block Client ID - DEPRECATED"
 msgstr ""
 
-#: lti_xblock.py:317
+#: lti_xblock.py:339
 msgid "DEPRECATED - This is now stored in the LtiConfiguration model."
 msgstr ""
 
-#: lti_xblock.py:320
+#: lti_xblock.py:342
 msgid "LTI 1.3 Block Key - DEPRECATED"
 msgstr ""
 
-#: lti_xblock.py:327
+#: lti_xblock.py:349
 msgid "Deep linking"
 msgstr ""
 
-#: lti_xblock.py:328
+#: lti_xblock.py:350
 msgid "Select True if you want to enable LTI Advantage Deep Linking."
 msgstr ""
 
-#: lti_xblock.py:333
+#: lti_xblock.py:355
 msgid "Deep Linking Launch URL"
 msgstr ""
 
-#: lti_xblock.py:337
+#: lti_xblock.py:359
 msgid ""
 "Enter the LTI Advantage Deep Linking Launch URL. If the tool does not "
 "specify one, use the same value as 'Tool Launch URL'."
 msgstr ""
 
-#: lti_xblock.py:342
+#: lti_xblock.py:364
 msgid "LTI Assignment and Grades Service"
 msgstr ""
 
-#: lti_xblock.py:344
+#: lti_xblock.py:366
 msgid "Disabled"
 msgstr ""
 
-#: lti_xblock.py:345
+#: lti_xblock.py:367
 msgid "Allow tools to submit grades only (declarative)"
 msgstr ""
 
-#: lti_xblock.py:346
+#: lti_xblock.py:368
 msgid "Allow tools to manage and submit grade (programmatic)"
 msgstr ""
 
-#: lti_xblock.py:351
+#: lti_xblock.py:373
 msgid ""
 "Enable the LTI-AGS service and select the functionality enabled for LTI "
 "tools. The 'declarative' mode (default) will provide a tool with a LineItem "
@@ -173,11 +194,11 @@ msgid ""
 "tools to manage, create and link the grades."
 msgstr ""
 
-#: lti_xblock.py:359
+#: lti_xblock.py:381
 msgid "LTI ID"
 msgstr ""
 
-#: lti_xblock.py:361
+#: lti_xblock.py:383
 #, python-brace-format
 msgid ""
 "Enter the LTI ID for the external LTI provider. This value must be the same "
@@ -186,11 +207,11 @@ msgid ""
 "documentation{anchor_close} for more details on this setting."
 msgstr ""
 
-#: lti_xblock.py:373
+#: lti_xblock.py:395
 msgid "LTI URL"
 msgstr ""
 
-#: lti_xblock.py:375
+#: lti_xblock.py:397
 #, python-brace-format
 msgid ""
 "Enter the URL of the external tool that this component launches. This "
@@ -199,11 +220,11 @@ msgid ""
 "this setting."
 msgstr ""
 
-#: lti_xblock.py:388
+#: lti_xblock.py:410
 msgid "Custom Parameters"
 msgstr ""
 
-#: lti_xblock.py:390
+#: lti_xblock.py:412
 #, python-brace-format
 msgid ""
 "Add the key/value pair for any custom parameters, such as the page your e-"
@@ -212,11 +233,11 @@ msgid ""
 "documentation{anchor_close} for more details on this setting."
 msgstr ""
 
-#: lti_xblock.py:400
+#: lti_xblock.py:422
 msgid "LTI Launch Target"
 msgstr ""
 
-#: lti_xblock.py:402
+#: lti_xblock.py:424
 msgid ""
 "Select Inline if you want the LTI content to open in an IFrame in the "
 "current page. Select Modal if you want the LTI content to open in a modal "
@@ -225,146 +246,146 @@ msgid ""
 "Tool is set to False."
 msgstr ""
 
-#: lti_xblock.py:416
+#: lti_xblock.py:438
 msgid "Button Text"
 msgstr ""
 
-#: lti_xblock.py:418
+#: lti_xblock.py:440
 msgid ""
 "Enter the text on the button used to launch the third party application. "
 "This setting is only used when Hide External Tool is set to False and LTI "
 "Launch Target is set to Modal or New Window."
 msgstr ""
 
-#: lti_xblock.py:426
+#: lti_xblock.py:448
 msgid "Inline Height"
 msgstr ""
 
-#: lti_xblock.py:428
+#: lti_xblock.py:450
 msgid ""
 "Enter the desired pixel height of the iframe which will contain the LTI "
 "tool. This setting is only used when Hide External Tool is set to False and "
 "LTI Launch Target is set to Inline."
 msgstr ""
 
-#: lti_xblock.py:436
+#: lti_xblock.py:458
 msgid "Modal Height"
 msgstr ""
 
-#: lti_xblock.py:438
+#: lti_xblock.py:460
 msgid ""
 "Enter the desired viewport percentage height of the modal overlay which will "
 "contain the LTI tool. This setting is only used when Hide External Tool is "
 "set to False and LTI Launch Target is set to Modal."
 msgstr ""
 
-#: lti_xblock.py:446
+#: lti_xblock.py:468
 msgid "Modal Width"
 msgstr ""
 
-#: lti_xblock.py:448
+#: lti_xblock.py:470
 msgid ""
 "Enter the desired viewport percentage width of the modal overlay which will "
 "contain the LTI tool. This setting is only used when Hide External Tool is "
 "set to False and LTI Launch Target is set to Modal."
 msgstr ""
 
-#: lti_xblock.py:456
+#: lti_xblock.py:478
 msgid "Scored"
 msgstr ""
 
-#: lti_xblock.py:457
+#: lti_xblock.py:479
 msgid ""
 "Select True if this component will receive a numerical score from the "
 "external LTI system."
 msgstr ""
 
-#: lti_xblock.py:464
+#: lti_xblock.py:486
 msgid ""
 "Enter the number of points possible for this component.  The default value "
 "is 1.0.  This setting is only used when Scored is set to True."
 msgstr ""
 
-#: lti_xblock.py:473
+#: lti_xblock.py:495
 msgid ""
 "The score kept in the xblock KVS -- duplicate of the published score in "
 "django DB"
 msgstr ""
 
-#: lti_xblock.py:478
+#: lti_xblock.py:500
 msgid "Comment as returned from grader, LTI2.0 spec"
 msgstr ""
 
-#: lti_xblock.py:483
+#: lti_xblock.py:505
 msgid "Hide External Tool"
 msgstr ""
 
-#: lti_xblock.py:485
+#: lti_xblock.py:507
 msgid ""
 "Select True if you want to use this component as a placeholder for syncing "
 "with an external grading  system rather than launch an external tool.  This "
 "setting hides the Launch button and any IFrames for this component."
 msgstr ""
 
-#: lti_xblock.py:493
+#: lti_xblock.py:515
 msgid "Accept grades past deadline"
 msgstr ""
 
-#: lti_xblock.py:494
+#: lti_xblock.py:516
 msgid ""
 "Select True to allow third party systems to post grades past the deadline."
 msgstr ""
 
-#: lti_xblock.py:502
+#: lti_xblock.py:524
 msgid "Request user's username"
 msgstr ""
 
 #. Translators: This is used to request the user's username for a third party service.
-#: lti_xblock.py:504
+#: lti_xblock.py:526
 msgid "Select True to request the user's username."
 msgstr ""
 
-#: lti_xblock.py:509
+#: lti_xblock.py:531
 msgid "Request user's email"
 msgstr ""
 
 #. Translators: This is used to request the user's email for a third party service.
-#: lti_xblock.py:511
+#: lti_xblock.py:533
 msgid "Select True to request the user's email address."
 msgstr ""
 
-#: lti_xblock.py:516
+#: lti_xblock.py:538
 msgid "Send extra parameters"
 msgstr ""
 
-#: lti_xblock.py:517
+#: lti_xblock.py:539
 msgid ""
 "Select True to send the extra parameters, which might contain Personally "
 "Identifiable Information. The processors are site-wide, please consult the "
 "site administrator if you have any questions."
 msgstr ""
 
-#: lti_xblock.py:578
+#: lti_xblock.py:602
 msgid "Custom Parameters must be a list"
 msgstr ""
 
-#: lti_xblock.py:717
+#: lti_xblock.py:712
 msgid ""
 "Could not parse LTI passport: {lti_passport!r}. Should be \"id:key:secret\" "
 "string."
 msgstr ""
 
-#: lti_xblock.py:733 lti_xblock.py:749
+#: lti_xblock.py:728 lti_xblock.py:744
 msgid "Could not get user id for current request"
 msgstr ""
 
-#: lti_xblock.py:854
+#: lti_xblock.py:849
 msgid ""
 "Could not parse custom parameter: {custom_parameter!r}. Should be \"x=y\" "
 "string."
 msgstr ""
 
-#: lti_xblock.py:1299
+#: lti_xblock.py:1304
 msgid "[LTI]: Real user not found against anon_id: {}"
 msgstr ""