diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 89f084b3f45741dcae370d11b523bf4f6e28ede5..84962963bd1ef0f598d9727ca3a08f5f1e3fdb7f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,10 @@ These are notable changes in edx-platform.  This is a rolling list of changes,
 in roughly chronological order, most recent first.  Add your entries at or near
 the top.  Include a label indicating the component affected.
 
+
+Blades: Added Learning Tools Interoperability (LTI) blade. Now LTI components 
+can be included to courses.
+
 LMS: Added alphabetical sorting of forum categories and subcategories.
 It is hidden behind a false defaulted course level flag.
 
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 724dc439d9843b01ecf8c63a0b8d957e71d8f097..deef87a4037ca2d3eeaeac21673a8c165a51b526 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -52,7 +52,8 @@ NOTE_COMPONENT_TYPES = ['notes']
 ADVANCED_COMPONENT_TYPES = [
     'annotatable',
     'word_cloud',
-    'graphical_slider_tool'
+    'graphical_slider_tool',
+    'lti',
 ] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES
 ADVANCED_COMPONENT_CATEGORY = 'advanced'
 ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules'
diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py
index ccbb7fb5bb12dcb33221754b20ac1321ac54c4c4..45ad7a7424ada512ae85a079753598e56e8d1fa5 100644
--- a/cms/djangoapps/contentstore/views/preview.py
+++ b/cms/djangoapps/contentstore/views/preview.py
@@ -81,7 +81,6 @@ def preview_component(request, location):
         component,
         'xmodule_edit.html'
     )
-
     return render_to_response('component.html', {
         'preview': get_preview_html(request, component, 0),
         'editor': component.runtime.render(component, None, 'studio_view').content,
@@ -104,7 +103,6 @@ def preview_module_system(request, preview_id, descriptor):
         return lms_field_data(descriptor._field_data, student_data)
 
     course_id = get_course_for_item(descriptor.location).location.course_id
-
     return ModuleSystem(
         ajax_url=reverse('preview_dispatch', args=[preview_id, descriptor.location.url(), '']).rstrip('/'),
         # TODO (cpennington): Do we want to track how instructors are using the preview problems?
@@ -118,6 +116,8 @@ def preview_module_system(request, preview_id, descriptor):
         xblock_field_data=preview_field_data,
         can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
         mixins=settings.XBLOCK_MIXINS,
+        course_id=course_id,
+        anonymous_student_id='student'
     )
 
 
diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py
index 704de15ea77760e1538f88f789f4b7db5514951d..6a24bf8f276de1747b300f6e7b9cc015c5dab516 100644
--- a/common/lib/xmodule/setup.py
+++ b/common/lib/xmodule/setup.py
@@ -56,6 +56,7 @@ setup(
             "hidden = xmodule.hidden_module:HiddenDescriptor",
             "raw = xmodule.raw_module:RawDescriptor",
             "crowdsource_hinter = xmodule.crowdsource_hinter:CrowdsourceHinterDescriptor",
+            "lti = xmodule.lti_module:LTIModuleDescriptor"
         ],
         'console_scripts': [
             'xmodule_assets = xmodule.static_content:main',
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index aca804d5e2e23fdba6ceedc82977f03e554dfd36..658a095d148026d4f4e32c26518dd0e9648b0485 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -153,6 +153,7 @@ class TextbookList(List):
 
 
 class CourseFields(object):
+    lti_passports = List(help="LTI tools passports as id:client_key:client_secret", scope=Scope.settings)
     textbooks = TextbookList(help="List of pairs of (title, url) for textbooks used in this course",
                              default=[], scope=Scope.content)
     wiki_slug = String(help="Slug that points to the wiki for this course", scope=Scope.content)
diff --git a/common/lib/xmodule/xmodule/css/lti/lti.scss b/common/lib/xmodule/xmodule/css/lti/lti.scss
new file mode 100644
index 0000000000000000000000000000000000000000..97a8f62d5450591ae948976b1dab90cb40cb9a06
--- /dev/null
+++ b/common/lib/xmodule/xmodule/css/lti/lti.scss
@@ -0,0 +1,30 @@
+div.lti {
+    // align center
+    margin: 0 auto;
+
+    h3.error_message {
+        display: block;
+    }
+
+    form.ltiLaunchForm {
+        display: none;
+    }
+
+    iframe.ltiLaunchFrame {
+        width: 100%;
+        height: 800px;
+        display: none;
+        border: 0px;
+        overflow-x: hidden;
+    }
+
+    &.rendered {
+        iframe.ltiLaunchFrame {
+            display: block;
+        }
+
+        h3.error_message {
+            display: none;
+        }
+    }
+}
diff --git a/common/lib/xmodule/xmodule/js/fixtures/lti.html b/common/lib/xmodule/xmodule/js/fixtures/lti.html
new file mode 100644
index 0000000000000000000000000000000000000000..e5e7ab3f3f0baa2f12f771061f8c5a84cb197682
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/fixtures/lti.html
@@ -0,0 +1,40 @@
+<div id="lti_id" class="lti">
+
+    <form
+        action=""
+        name="ltiLaunchForm"
+        class="ltiLaunchForm"
+        method="post"
+        target="ltiLaunchFrame"
+        encType="application/x-www-form-urlencoded"
+    >
+
+        <input type="hidden" name="launch_presentation_return_url" value="">
+        <input type="hidden" name="lis_outcome_service_url" value="">
+        <input type="hidden" name="lis_result_sourcedid" value="">
+        <input type="hidden" name="lti_message_type" value="basic-lti-launch-request">
+        <input type="hidden" name="lti_version" value="LTI-1p0">
+        <input type="hidden" name="oauth_callback" value="about:blank">
+        <input type="hidden" name="oauth_consumer_key" value=""/>
+        <input type="hidden" name="oauth_nonce" value=""/>
+        <input type="hidden" name="oauth_signature_method" value="HMAC-SHA1"/>
+        <input type="hidden" name="oauth_timestamp" value=""/>
+        <input type="hidden" name="oauth_version" value="1.0"/>
+        <input type="hidden" name="user_id" value="default_user_id">
+        <input type="hidden" name="oauth_signature" value=""/>
+
+        <input type="submit" value="Press to Launch" />
+    </form>
+
+    <h3 class="error_message">
+        Please provide launch_url. Click "Edit", and fill in the
+        required fields.
+    </h3>
+
+    <iframe
+        name="ltiLaunchFrame"
+        class="ltiLaunchFrame"
+        src=""
+    ></iframe>
+
+</div>
diff --git a/common/lib/xmodule/xmodule/js/spec/lti/constructor.js b/common/lib/xmodule/xmodule/js/spec/lti/constructor.js
new file mode 100644
index 0000000000000000000000000000000000000000..0a73496bedfcb2ad04cad6481d6cf6a4d8511e65
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/spec/lti/constructor.js
@@ -0,0 +1,84 @@
+/**
+ * File: constructor.js
+ *
+ * Purpose: Jasmine tests for LTI module (front-end part).
+ *
+ *
+ * The front-end part of the LTI module is really simple. If an action
+ * is set for the hidden LTI form, then it is submited, and the results are
+ * redirected to an iframe.
+ *
+ * We will test that the form is only submited when the action is set (i.e.
+ * not empty).
+ *
+ * Other aspects of LTI module will be covered by Python unit tests and
+ * acceptance tests.
+ *
+ */
+
+/*
+ * "Hence that general is skilful in attack whose opponent does not know what
+ * to defend; and he is skilful in defense whose opponent does not know what
+ * to attack."
+ *
+ * ~ Sun Tzu
+ */
+
+(function () {
+    describe('LTI', function () {
+        describe('constructor', function () {
+            describe('before settings were filled in', function () {
+                var element, errorMessage, frame;
+
+                // This function will be executed before each of the it() specs
+                // in this suite.
+                beforeEach(function () {
+                    loadFixtures('lti.html');
+
+                    element = $('#lti_id');
+                    errorMessage = element.find('.error_message');
+                    form = element.find('.ltiLaunchForm');
+                    frame = element.find('.ltiLaunchFrame');
+
+                    spyOnEvent(form, 'submit');
+
+                    LTI(element);
+                });
+
+                it(
+                    'when URL setting is filled form is not submited',
+                    function () {
+
+                    expect('submit').not.toHaveBeenTriggeredOn(form);
+                });
+            });
+
+            describe('After the settings were filled in', function () {
+                var element, errorMessage, frame;
+
+                // This function will be executed before each of the it() specs
+                // in this suite.
+                beforeEach(function () {
+                    loadFixtures('lti.html');
+
+                    element = $('#lti_id');
+                    errorMessage = element.find('.error_message');
+                    form = element.find('.ltiLaunchForm');
+                    frame = element.find('.ltiLaunchFrame');
+
+                    spyOnEvent(form, 'submit');
+
+                    // The user "fills in" the necessary settings, and the
+                    // form will get an action URL.
+                    form.attr('action', 'http://www.example.com/');
+
+                    LTI(element);
+                });
+
+                it('when URL setting is filled form is submited', function () {
+                    expect('submit').toHaveBeenTriggeredOn(form);
+                });
+            });
+        });
+    });
+}());
diff --git a/common/lib/xmodule/xmodule/js/src/lti/lti.js b/common/lib/xmodule/xmodule/js/src/lti/lti.js
new file mode 100644
index 0000000000000000000000000000000000000000..e5b6885e1bbf534a1005ed625b011ae78d35bfe6
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/lti/lti.js
@@ -0,0 +1,26 @@
+window.LTI = (function () {
+    // Function initialize(element)
+    //
+    // Initialize the LTI iframe.
+    function initialize(element) {
+        var form;
+
+        // In cms (Studio) the element is already a jQuery object. In lms it is
+        // a DOM object.
+        //
+        // To make sure that there is no error, we pass it through the $()
+        // function. This will make it a jQuery object if it isn't already so.
+        element = $(element);
+
+        form = element.find('.ltiLaunchForm');
+
+        // If the Form's action attribute is set (i.e. we can perform a normal
+        // submit), then we submit the form and make the frame shown.
+        if (form.attr('action')) {
+            form.submit();
+            element.find('.lti').addClass('rendered')
+        }
+    }
+
+    return initialize;
+}());
diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc07cea97ebb3759b59930c3f9fd988a149e88f9
--- /dev/null
+++ b/common/lib/xmodule/xmodule/lti_module.py
@@ -0,0 +1,249 @@
+"""
+Module that allows to insert LTI tools to page.
+
+Module uses current edx-platform 0.14.2 version of requests (oauth part).
+Please update code when upgrading requests.
+
+Protocol is oauth1, LTI version is 1.1.1:
+http://www.imsglobal.org/LTI/v1p1p1/ltiIMGv1p1p1.html
+"""
+
+import logging
+import requests
+import urllib
+
+from xmodule.editing_module import MetadataOnlyEditingDescriptor
+from xmodule.x_module import XModule
+from xmodule.course_module import CourseDescriptor
+from pkg_resources import resource_string
+from xblock.core import String, Scope, List
+
+log = logging.getLogger(__name__)
+
+
+class LTIError(Exception):
+    pass
+
+
+class LTIFields(object):
+    """
+    Fields to define and obtain LTI tool from provider are set here,
+    except credentials, which should be set in course settings::
+
+    `lti_id` is id to connect tool with credentials in course settings.
+    `launch_url` is launch url of tool.
+    `custom_parameters` are additional parameters to navigate to proper book and book page.
+
+    For example, for Vitalsource provider, `launch_url` should be
+    *https://bc-staging.vitalsource.com/books/book*,
+    and to get to proper book and book page, you should set custom parameters as::
+
+        vbid=put_book_id_here
+        book_location=page/put_page_number_here
+
+    """
+    lti_id = String(help="Id of the tool", default='', scope=Scope.settings)
+    launch_url = String(help="URL of the tool", default='', scope=Scope.settings)
+    custom_parameters = List(help="Custom parameters (vbid, book_location, etc..)", scope=Scope.settings)
+
+
+class LTIModule(LTIFields, XModule):
+    '''
+    Module provides LTI integration to course.
+
+    Except usual xmodule structure it proceeds with oauth signing.
+    How it works::
+
+    1. Get credentials from course settings.
+
+    2.  There is minimal set of parameters need to be signed (presented for Vitalsource)::
+
+            user_id
+            oauth_callback
+            lis_outcome_service_url
+            lis_result_sourcedid
+            launch_presentation_return_url
+            lti_message_type
+            lti_version
+            role
+            *+ all custom parameters*
+
+        These parameters should be encoded and signed by *oauth1* together with
+        `launch_url` and *POST* request type.
+
+    3. Signing proceeds with client key/secret pair obtained from course settings.
+        That pair should be obtained from LTI provider and set into course settings by course author.
+        After that signature and other oauth data are generated.
+
+         Oauth data which is generated after signing is usual::
+
+            oauth_callback
+            oauth_nonce
+            oauth_consumer_key
+            oauth_signature_method
+            oauth_timestamp
+            oauth_version
+
+
+    4. All that data is passed to form and sent to LTI provider server by browser via
+        autosubmit via javascript.
+
+        Form example::
+
+            <form
+                    action="${launch_url}"
+                    name="ltiLaunchForm"
+                    class="ltiLaunchForm"
+                    method="post"
+                    target="ltiLaunchFrame"
+                    encType="application/x-www-form-urlencoded"
+                >
+                    <input name="launch_presentation_return_url" value="" />
+                    <input name="lis_outcome_service_url" value="" />
+                    <input name="lis_result_sourcedid" value="" />
+                    <input name="lti_message_type" value="basic-lti-launch-request" />
+                    <input name="lti_version" value="LTI-1p0" />
+                    <input name="oauth_callback" value="about:blank" />
+                    <input name="oauth_consumer_key" value="${oauth_consumer_key}" />
+                    <input name="oauth_nonce" value="${oauth_nonce}" />
+                    <input name="oauth_signature_method" value="HMAC-SHA1" />
+                    <input name="oauth_timestamp" value="${oauth_timestamp}" />
+                    <input name="oauth_version" value="1.0" />
+                    <input name="user_id" value="${user_id}" />
+                    <input name="role" value="student" />
+                    <input name="oauth_signature" value="${oauth_signature}" />
+
+                    <input name="custom_1" value="${custom_param_1_value}" />
+                    <input name="custom_2" value="${custom_param_2_value}" />
+                    <input name="custom_..." value="${custom_param_..._value}" />
+
+                    <input type="submit" value="Press to Launch" />
+                </form>
+
+    5. LTI provider has same secret key and it signs data string via *oauth1* and compares signatures.
+
+        If signatures are correct, LTI provider redirects iframe source to LTI tool web page,
+        and LTI tool is rendered to iframe inside course.
+
+        Otherwise error message from LTI provider is generated.
+    '''
+
+    js = {'js': [resource_string(__name__, 'js/src/lti/lti.js')]}
+    css = {'scss': [resource_string(__name__, 'css/lti/lti.scss')]}
+    js_module_name = "LTI"
+
+    def get_html(self):
+        """
+        Renders parameters to template.
+        """
+
+        # Obtains client_key and client_secret credentials from current course:
+        course_id = self.runtime.course_id
+        course_location = CourseDescriptor.id_to_location(course_id)
+        course = self.descriptor.runtime.modulestore.get_item(course_location)
+        client_key = client_secret = ''
+        for lti_passport in course.lti_passports:
+            try:
+                lti_id, key, secret = lti_passport.split(':')
+            except ValueError:
+                raise LTIError('Could not parse LTI passport: {0!r}. \
+                    Should be "id:key:secret" string.'.format(lti_passport))
+            if lti_id == self.lti_id:
+                client_key, client_secret = key, secret
+                break
+
+        # parsing custom parameters to dict
+        custom_parameters = {}
+        for custom_parameter in self.custom_parameters:
+            try:
+                param_name, param_value = custom_parameter.split('=', 1)
+            except ValueError:
+                raise LTIError('Could not parse custom parameter: {0!r}. \
+                    Should be "x=y" string.'.format(custom_parameter))
+
+            # LTI specs:  'custom_' should be prepended before each custom parameter
+            custom_parameters[u'custom_' + unicode(param_name)] = unicode(param_value)
+
+        input_fields = self.oauth_params(
+            custom_parameters,
+            client_key,
+            client_secret
+        )
+
+        context = {
+            'input_fields': input_fields,
+
+            # these params do not participate in oauth signing
+            'launch_url': self.launch_url,
+            'element_id': self.location.html_id(),
+            'element_class': self.location.category,
+        }
+
+        return self.system.render_template('lti.html', context)
+
+    def oauth_params(self, custom_parameters, client_key, client_secret):
+        """
+        Signs request and returns signature and oauth parameters.
+
+        `custom_paramters` is dict of parsed `custom_parameter` field
+
+        `client_key` and `client_secret` are LTI tool credentials.
+
+        Also *anonymous student id* is passed to template and therefore to LTI provider.
+        """
+
+        client = requests.auth.Client(
+            client_key=unicode(client_key),
+            client_secret=unicode(client_secret)
+        )
+
+        user_id = self.runtime.anonymous_student_id
+        assert user_id is not None
+
+        # must have parameters for correct signing from LTI:
+        body = {
+            u'user_id': user_id,
+            u'oauth_callback': u'about:blank',
+            u'lis_outcome_service_url': '',
+            u'lis_result_sourcedid': '',
+            u'launch_presentation_return_url': '',
+            u'lti_message_type': u'basic-lti-launch-request',
+            u'lti_version': 'LTI-1p0',
+            u'role': u'student'
+        }
+
+        # appending custom parameter for signing
+        body.update(custom_parameters)
+
+        # This is needed for body encoding:
+        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+
+        __, headers, __ = client.sign(
+            unicode(self.launch_url),
+            http_method=u'POST',
+            body=body,
+            headers=headers)
+        params = headers['Authorization']
+        # parse headers to pass to template as part of context:
+        params = dict([param.strip().replace('"', '').split('=') for param in params.split(',')])
+
+        params[u'oauth_nonce'] = params[u'OAuth oauth_nonce']
+        del params[u'OAuth oauth_nonce']
+
+        # 0.14.2 (current) version of requests oauth library encodes signature,
+        # with 'Content-Type': 'application/x-www-form-urlencoded'
+        # so '='' becomes '%3D'.
+        # We send form via browser, so browser will encode it again,
+        # So we need to decode signature back:
+        params[u'oauth_signature'] = urllib.unquote(params[u'oauth_signature']).decode('utf8')
+
+        # add lti parameters to oauth parameters for sending in form
+        params.update(body)
+        return params
+
+
+class LTIModuleDescriptor(LTIFields, MetadataOnlyEditingDescriptor):
+    """
+    LTIModuleDescriptor provides no export/import to xml.
+    """
+    module_class = LTIModule
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py
index 4ad801aef84be54c036809ab022817b7e927af14..c8228c5e3eb45c697f448d639d933ce42573e1a5 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py
@@ -27,7 +27,7 @@ class XModuleCourseFactory(Factory):
         store = editable_modulestore('direct')
 
         # Write the data to the mongo datastore
-        new_course = store.create_xmodule(location)
+        new_course = store.create_xmodule(location, metadata=kwargs.get('metadata', None))
 
         # This metadata code was copied from cms/djangoapps/contentstore/views.py
         if display_name is not None:
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index fefa668a56aa4205e1e4bda3d7c736f713f9f0e7..b7e5ea8435c5f9a04299a2a8cf9230414d0bdd78 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -40,7 +40,7 @@ open_ended_grading_interface = {
     }
 
 
-def get_test_system():
+def get_test_system(course_id=''):
     """
     Construct a test ModuleSystem instance.
 
@@ -66,7 +66,8 @@ def get_test_system():
         node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
         xblock_field_data=lambda descriptor: descriptor._field_data,
         anonymous_student_id='student',
-        open_ended_grading_interface=open_ended_grading_interface
+        open_ended_grading_interface=open_ended_grading_interface,
+        course_id=course_id,
     )
 
 
diff --git a/docs/developers/source/xmodule.rst b/docs/developers/source/xmodule.rst
index 008a1303d29617cf2de892220db7aab4b90255b0..77ee2ea684fa67757d01f26abf8420bd45ad0997 100644
--- a/docs/developers/source/xmodule.rst
+++ b/docs/developers/source/xmodule.rst
@@ -95,6 +95,14 @@ Html
     :members:
     :show-inheritance:
 
+
+LTI
+===
+
+.. automodule:: xmodule.lti_module
+    :members:
+    :show-inheritance:
+
 Mako
 ====
 
diff --git a/lms/djangoapps/courseware/features/lti.feature b/lms/djangoapps/courseware/features/lti.feature
new file mode 100644
index 0000000000000000000000000000000000000000..abdcfdb7046032e5e43912a5b5696c8b27f40cf7
--- /dev/null
+++ b/lms/djangoapps/courseware/features/lti.feature
@@ -0,0 +1,17 @@
+Feature: LTI component
+  As a student, I want to view LTI component in LMS.
+
+  Scenario: LTI component in LMS is not rendered
+  Given the course has correct LTI credentials
+  And the course has an LTI component with incorrect fields
+  Then I view the LTI and it is not rendered
+
+  Scenario: LTI component in LMS is rendered
+  Given the course has correct LTI credentials
+  And the course has an LTI component filled with correct fields
+  Then I view the LTI and it is rendered
+
+  Scenario: LTI component in LMS is rendered incorrectly
+  Given the course has incorrect LTI credentials
+  And the course has an LTI component filled with correct fields
+  Then I view the LTI but incorrect_signature warning is rendered
\ No newline at end of file
diff --git a/lms/djangoapps/courseware/features/lti.py b/lms/djangoapps/courseware/features/lti.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e91d5ed02664bc29d7dde514e39e5974cde138c
--- /dev/null
+++ b/lms/djangoapps/courseware/features/lti.py
@@ -0,0 +1,188 @@
+#pylint: disable=C0111
+
+from django.contrib.auth.models import User
+from lettuce import world, step
+from lettuce.django import django_url
+from common import course_id
+
+from student.models import CourseEnrollment
+
+
+@step('I view the LTI and it is not rendered$')
+def lti_is_not_rendered(_step):
+    # lti div has no class rendered
+    assert world.is_css_not_present('div.lti.rendered')
+
+    # error is shown
+    assert world.css_visible('.error_message')
+
+    # iframe is not visible
+    assert not world.css_visible('iframe')
+
+    #inside iframe test content is not presented
+    with world.browser.get_iframe('ltiLaunchFrame') as iframe:
+        # iframe does not contain functions from terrain/ui_helpers.py
+        assert iframe.is_element_not_present_by_css('.result', wait_time=5)
+
+
+@step('I view the LTI and it is rendered$')
+def lti_is_rendered(_step):
+    # lti div has class rendered
+    assert world.is_css_present('div.lti.rendered')
+
+    # error is hidden
+    assert not world.css_visible('.error_message')
+
+    # iframe is visible
+    assert world.css_visible('iframe')
+
+    #inside iframe test content is presented
+    with world.browser.get_iframe('ltiLaunchFrame') as iframe:
+        # iframe does not contain functions from terrain/ui_helpers.py
+        assert iframe.is_element_present_by_css('.result', wait_time=5)
+        assert ("This is LTI tool. Success." == world.retry_on_exception(
+            lambda: iframe.find_by_css('.result')[0].text,
+            max_attempts=5
+        ))
+
+
+@step('I view the LTI but incorrect_signature warning is rendered$')
+def incorrect_lti_is_rendered(_step):
+    # lti div has class rendered
+    assert world.is_css_present('div.lti.rendered')
+
+    # error is hidden
+    assert not world.css_visible('.error_message')
+
+    # iframe is visible
+    assert world.css_visible('iframe')
+
+    #inside iframe test content is presented
+    with world.browser.get_iframe('ltiLaunchFrame') as iframe:
+        # iframe does not contain functions from terrain/ui_helpers.py
+        assert iframe.is_element_present_by_css('.result', wait_time=5)
+        assert ("Wrong LTI signature" == world.retry_on_exception(
+            lambda: iframe.find_by_css('.result')[0].text,
+            max_attempts=5
+        ))
+
+
+@step('the course has correct LTI credentials$')
+def set_correct_lti_passport(_step):
+    coursenum = 'test_course'
+    metadata = {
+        'lti_passports': ["correct_lti_id:{}:{}".format(
+            world.lti_server.oauth_settings['client_key'],
+            world.lti_server.oauth_settings['client_secret']
+        )]
+    }
+    i_am_registered_for_the_course(coursenum, metadata)
+
+
+@step('the course has incorrect LTI credentials$')
+def set_incorrect_lti_passport(_step):
+    coursenum = 'test_course'
+    metadata = {
+        'lti_passports': ["test_lti_id:{}:{}".format(
+            world.lti_server.oauth_settings['client_key'],
+            "incorrect_lti_secret_key"
+        )]
+    }
+    i_am_registered_for_the_course(coursenum, metadata)
+
+
+@step('the course has an LTI component filled with correct fields$')
+def add_correct_lti_to_course(_step):
+    category = 'lti'
+    world.ItemFactory.create(
+        # parent_location=section_location(course),
+        parent_location=world.scenario_dict['SEQUENTIAL'].location,
+        category=category,
+        display_name='LTI',
+        metadata={
+            'lti_id': 'correct_lti_id',
+            'launch_url': world.lti_server.oauth_settings['lti_base'] + world.lti_server.oauth_settings['lti_endpoint']
+        }
+    )
+    course = world.scenario_dict["COURSE"]
+    chapter_name = world.scenario_dict['SECTION'].display_name.replace(
+        " ", "_")
+    section_name = chapter_name
+    path = "/courses/{org}/{num}/{name}/courseware/{chapter}/{section}".format(
+        org=course.org,
+        num=course.number,
+        name=course.display_name.replace(' ', '_'),
+        chapter=chapter_name,
+        section=section_name)
+    url = django_url(path)
+
+    world.browser.visit(url)
+
+
+@step('the course has an LTI component with incorrect fields$')
+def add_incorrect_lti_to_course(_step):
+    category = 'lti'
+    world.ItemFactory.create(
+        parent_location=world.scenario_dict['SEQUENTIAL'].location,
+        category=category,
+        display_name='LTI',
+        metadata={
+            'lti_id': 'incorrect_lti_id',
+            'lti_url': world.lti_server.oauth_settings['lti_base'] + world.lti_server.oauth_settings['lti_endpoint']
+        }
+    )
+    course = world.scenario_dict["COURSE"]
+    chapter_name = world.scenario_dict['SECTION'].display_name.replace(
+        " ", "_")
+    section_name = chapter_name
+    path = "/courses/{org}/{num}/{name}/courseware/{chapter}/{section}".format(
+        org=course.org,
+        num=course.number,
+        name=course.display_name.replace(' ', '_'),
+        chapter=chapter_name,
+        section=section_name)
+    url = django_url(path)
+
+    world.browser.visit(url)
+
+
+def create_course(course, metadata):
+
+    # First clear the modulestore so we don't try to recreate
+    # the same course twice
+    # This also ensures that the necessary templates are loaded
+    world.clear_courses()
+
+    # Create the course
+    # We always use the same org and display name,
+    # but vary the course identifier (e.g. 600x or 191x)
+    world.scenario_dict['COURSE'] = world.CourseFactory.create(
+        org='edx',
+        number=course,
+        display_name='Test Course',
+        metadata=metadata
+    )
+
+    # Add a section to the course to contain problems
+    world.scenario_dict['SECTION'] = world.ItemFactory.create(
+        parent_location=world.scenario_dict['COURSE'].location,
+        display_name='Test Section'
+    )
+    world.scenario_dict['SEQUENTIAL'] = world.ItemFactory.create(
+        parent_location=world.scenario_dict['SECTION'].location,
+        category='sequential',
+        display_name='Test Section')
+
+
+def i_am_registered_for_the_course(course, metadata):
+    # Create the course
+    create_course(course, metadata)
+
+    # Create the user
+    world.create_user('robot', 'test')
+    usr = User.objects.get(username='robot')
+
+    # If the user is not already enrolled, enroll the user.
+    CourseEnrollment.enroll(usr, course_id(course))
+
+    world.log_in(username='robot', password='test')
diff --git a/lms/djangoapps/courseware/features/lti_setup.py b/lms/djangoapps/courseware/features/lti_setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a6c4590dd9e6e78fed924dbeb1c38c7ca293f14
--- /dev/null
+++ b/lms/djangoapps/courseware/features/lti_setup.py
@@ -0,0 +1,50 @@
+#pylint: disable=C0111
+#pylint: disable=W0621
+
+from courseware.mock_lti_server.mock_lti_server import MockLTIServer
+from lettuce import before, after, world
+from django.conf import settings
+import threading
+
+from logging import getLogger
+logger = getLogger(__name__)
+
+
+@before.all
+def setup_mock_lti_server():
+
+    server_host = '127.0.0.1'
+
+    # Add +1 to XQUEUE random port number
+    server_port = settings.XQUEUE_PORT + 1
+
+    address = (server_host, server_port)
+
+    # Create the mock server instance
+    server = MockLTIServer(address)
+    logger.debug("LTI server started at {} port".format(str(server_port)))
+    # Start the server running in a separate daemon thread
+    # Because the thread is a daemon, it will terminate
+    # when the main thread terminates.
+    server_thread = threading.Thread(target=server.serve_forever)
+    server_thread.daemon = True
+    server_thread.start()
+
+    server.oauth_settings = {
+        'client_key': 'test_client_key',
+        'client_secret': 'test_client_secret',
+        'lti_base':  'http://{}:{}/'.format(server_host, server_port),
+        'lti_endpoint': 'correct_lti_endpoint'
+    }
+
+    # Store the server instance in lettuce's world
+    # so that other steps can access it
+    # (and we can shut it down later)
+    world.lti_server = server
+
+
+@after.all
+def teardown_mock_lti_server(total):
+
+    # Stop the LTI server and free up the port
+    world.lti_server.shutdown()
diff --git a/lms/djangoapps/courseware/mock_lti_server/__init__.py b/lms/djangoapps/courseware/mock_lti_server/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lms/djangoapps/courseware/mock_lti_server/mock_lti_server.py b/lms/djangoapps/courseware/mock_lti_server/mock_lti_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba9cea84d67a4ca03488f2569a864d552b41df1a
--- /dev/null
+++ b/lms/djangoapps/courseware/mock_lti_server/mock_lti_server.py
@@ -0,0 +1,167 @@
+from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+import urlparse
+from requests.packages.oauthlib.oauth1.rfc5849 import signature
+import mock
+from logging import getLogger
+logger = getLogger(__name__)
+
+
+class MockLTIRequestHandler(BaseHTTPRequestHandler):
+    '''
+    A handler for LTI POST requests.
+    '''
+
+    protocol = "HTTP/1.0"
+
+    def do_HEAD(self):
+        self._send_head()
+
+    def do_POST(self):
+        '''
+        Handle a POST request from the client and sends response back.
+        '''
+        self._send_head()
+
+        post_dict = self._post_dict()  # Retrieve the POST data
+
+        logger.debug("LTI provider received POST request {} to path {}".format(
+            str(post_dict),
+            self.path)
+        )  # Log the request
+
+        # Respond only to requests with correct lti endpoint:
+        if self._is_correct_lti_request():
+            correct_keys = [
+                'user_id',
+                'role',
+                'oauth_nonce',
+                'oauth_timestamp',
+                'oauth_consumer_key',
+                'lti_version',
+                'oauth_signature_method',
+                'oauth_version',
+                'oauth_signature',
+                'lti_message_type',
+                'oauth_callback',
+                'lis_outcome_service_url',
+                'lis_result_sourcedid',
+                'launch_presentation_return_url'
+            ]
+
+            if sorted(correct_keys) != sorted(post_dict.keys()):
+                status_message = "Incorrect LTI header"
+            else:
+                params = {k: v for k, v in post_dict.items() if k != 'oauth_signature'}
+                if self.server.check_oauth_signature(params, post_dict['oauth_signature']):
+                    status_message = "This is LTI tool. Success."
+                else:
+                    status_message = "Wrong LTI signature"
+        else:
+            status_message = "Invalid request URL"
+
+        self._send_response(status_message)
+
+    def _send_head(self):
+        '''
+        Send the response code and MIME headers
+        '''
+        if self._is_correct_lti_request():
+            self.send_response(200)
+        else:
+            self.send_response(500)
+
+        self.send_header('Content-type', 'text/html')
+        self.end_headers()
+
+    def _post_dict(self):
+        '''
+        Retrieve the POST parameters from the client as a dictionary
+        '''
+        try:
+            length = int(self.headers.getheader('content-length'))
+            post_dict = urlparse.parse_qs(self.rfile.read(length), keep_blank_values=True)
+            # The POST dict will contain a list of values for each key.
+            # None of our parameters are lists, however, so we map [val] --> val.
+            # If the list contains multiple entries, we pick the first one
+            post_dict = {key: val[0] for key, val in post_dict.items()}
+        except:
+            # We return an empty dict here, on the assumption
+            # that when we later check that the request has
+            # the correct fields, it won't find them,
+            # and will therefore send an error response
+            return {}
+        return post_dict
+
+    def _send_response(self, message):
+        '''
+        Send message back to the client
+        '''
+        response_str = """<html><head><title>TEST TITLE</title></head>
+        <body>
+        <div><h2>IFrame loaded</h2> \
+        <h3>Server response is:</h3>\
+        <h3 class="result">{}</h3></div>
+        </body></html>""".format(message)
+
+        # Log the response
+        logger.debug("LTI: sent response {}".format(response_str))
+
+        self.wfile.write(response_str)
+
+    def _is_correct_lti_request(self):
+        '''If url to LTI tool is correct.'''
+        return self.server.oauth_settings['lti_endpoint'] in self.path
+
+
+class MockLTIServer(HTTPServer):
+    '''
+    A mock LTI provider server that responds
+    to POST requests to localhost.
+    '''
+
+    def __init__(self, address):
+        '''
+        Initialize the mock XQueue server instance.
+
+        *address* is the (host, host's port to listen to) tuple.
+        '''
+        handler = MockLTIRequestHandler
+        HTTPServer.__init__(self, address, handler)
+
+    def shutdown(self):
+        '''
+        Stop the server and free up the port
+        '''
+        # First call superclass shutdown()
+        HTTPServer.shutdown(self)
+        # We also need to manually close the socket
+        self.socket.close()
+
+    def check_oauth_signature(self, params, client_signature):
+        '''
+        Checks oauth signature from client.
+
+        `params` are params from post request except signature,
+        `client_signature` is signature from request.
+
+        Builds mocked request and verifies hmac-sha1 signing::
+            1. builds string to sign from `params`, `url` and `http_method`.
+            2. signs it with `client_secret` which comes from server settings.
+            3. obtains signature after sign and then compares it with request.signature
+            (request signature comes form client in request)
+
+        Returns `True` if signatures are correct, otherwise `False`.
+
+        '''
+        client_secret = unicode(self.oauth_settings['client_secret'])
+        url = self.oauth_settings['lti_base'] + self.oauth_settings['lti_endpoint']
+
+        request = mock.Mock()
+
+        request.params = [(unicode(k), unicode(v)) for k, v in params.items()]
+        request.uri = unicode(url)
+        request.http_method = u'POST'
+        request.signature = unicode(client_signature)
+
+        return signature.verify_hmac_sha1(request, client_secret)
+
diff --git a/lms/djangoapps/courseware/mock_lti_server/test_mock_lti_server.py b/lms/djangoapps/courseware/mock_lti_server/test_mock_lti_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..99650d5faae31fa9c4baa9966e41a61b267b66b4
--- /dev/null
+++ b/lms/djangoapps/courseware/mock_lti_server/test_mock_lti_server.py
@@ -0,0 +1,75 @@
+"""
+Test for Mock_LTI_Server
+"""
+import unittest
+import threading
+import urllib
+from mock_lti_server import MockLTIServer
+
+from nose.plugins.skip import SkipTest
+
+
+class MockLTIServerTest(unittest.TestCase):
+    '''
+    A mock version of the LTI provider server that listens on a local
+    port and responds with pre-defined grade messages.
+
+    Used for lettuce BDD tests in lms/courseware/features/lti.feature
+    '''
+
+    def setUp(self):
+
+        # This is a test of the test setup,
+        # so it does not need to run as part of the unit test suite
+        # You can re-enable it by commenting out the line below
+        # raise SkipTest
+
+        # Create the server
+        server_port = 8034
+        server_host = '127.0.0.1'
+        address = (server_host, server_port)
+        self.server = MockLTIServer(address)
+        self.server.oauth_settings = {
+            'client_key': 'test_client_key',
+            'client_secret': 'test_client_secret',
+            'lti_base':  'http://{}:{}/'.format(server_host, server_port),
+            'lti_endpoint': 'correct_lti_endpoint'
+        }
+        # Start the server in a separate daemon thread
+        server_thread = threading.Thread(target=self.server.serve_forever)
+        server_thread.daemon = True
+        server_thread.start()
+
+    def tearDown(self):
+
+        # Stop the server, freeing up the port
+        self.server.shutdown()
+
+    def test_request(self):
+        """
+        Tests that LTI server processes request with right program
+        path,  and responses with incorrect signature.
+        """
+        request = {
+            'user_id': 'default_user_id',
+            'role': 'student',
+            'oauth_nonce': '',
+            'oauth_timestamp': '',
+            'oauth_consumer_key': 'client_key',
+            'lti_version': 'LTI-1p0',
+            'oauth_signature_method': 'HMAC-SHA1',
+            'oauth_version': '1.0',
+            'oauth_signature': '',
+            'lti_message_type': 'basic-lti-launch-request',
+            'oauth_callback': 'about:blank',
+            'launch_presentation_return_url': '',
+            'lis_outcome_service_url': '',
+            'lis_result_sourcedid': ''
+        }
+
+        response_handle = urllib.urlopen(
+            self.server.oauth_settings['lti_base'] + self.server.oauth_settings['lti_endpoint'],
+            urllib.urlencode(request)
+        )
+        response = response_handle.read()
+        self.assertTrue('Wrong LTI signature' in response)
diff --git a/lms/djangoapps/courseware/tests/__init__.py b/lms/djangoapps/courseware/tests/__init__.py
index 88129cc8d1c6f04e9aacf88b5bf2d11f811cb873..0a4d3508b841f7e05c94aff5c88f3fe3f6283aa4 100644
--- a/lms/djangoapps/courseware/tests/__init__.py
+++ b/lms/djangoapps/courseware/tests/__init__.py
@@ -86,7 +86,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
             data=self.DATA
         )
 
-        self.runtime = get_test_system()
+        self.runtime = get_test_system(course_id=self.course.id)
         # Allow us to assert that the template was called in the same way from
         # different code paths while maintaining the type returned by render_template
         self.runtime.render_template = lambda template, context: u'{!r}, {!r}'.format(template, sorted(context.items()))
diff --git a/lms/djangoapps/courseware/tests/test_lti.py b/lms/djangoapps/courseware/tests/test_lti.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2b4ea686700438802e075663583b4fce595c374
--- /dev/null
+++ b/lms/djangoapps/courseware/tests/test_lti.py
@@ -0,0 +1,79 @@
+"""LTI integration tests"""
+
+import requests
+from . import BaseTestXmodule
+from collections import OrderedDict
+import mock
+
+
+class TestLTI(BaseTestXmodule):
+    """
+    Integration test for lti xmodule.
+
+    It checks overall code, by assuring that context that goes to template is correct.
+    As part of that, checks oauth signature generation by mocking signing function of `requests` library.
+    """
+    CATEGORY = "lti"
+
+    def setUp(self):
+        """
+        Mock oauth1 signing of requests library for testing.
+        """
+        super(TestLTI, self).setUp()
+        mocked_nonce = u'135685044251684026041377608307'
+        mocked_timestamp = u'1234567890'
+        mocked_signature_after_sign = u'my_signature%3D'
+        mocked_decoded_signature = u'my_signature='
+
+        self.correct_headers = {
+            u'oauth_callback': u'about:blank',
+            u'lis_outcome_service_url': '',
+            u'lis_result_sourcedid': '',
+            u'launch_presentation_return_url': '',
+            u'lti_message_type': u'basic-lti-launch-request',
+            u'lti_version': 'LTI-1p0',
+
+            u'oauth_nonce': mocked_nonce,
+            u'oauth_timestamp': mocked_timestamp,
+            u'oauth_consumer_key': u'',
+            u'oauth_signature_method': u'HMAC-SHA1',
+            u'oauth_version': u'1.0',
+            u'user_id': self.runtime.anonymous_student_id,
+            u'role': u'student',
+            u'oauth_signature': mocked_decoded_signature
+        }
+
+        saved_sign = requests.auth.Client.sign
+
+        def mocked_sign(self, *args, **kwargs):
+            """
+            Mocked oauth1 sign function.
+            """
+            # self is <oauthlib.oauth1.rfc5849.Client object> here:
+            __, headers, __ = saved_sign(self, *args, **kwargs)
+            # we should replace nonce, timestamp and signed_signature in headers:
+            old = headers[u'Authorization']
+            old_parsed = OrderedDict([param.strip().replace('"', '').split('=') for param in old.split(',')])
+            old_parsed[u'OAuth oauth_nonce'] = mocked_nonce
+            old_parsed[u'oauth_timestamp'] = mocked_timestamp
+            old_parsed[u'oauth_signature'] = mocked_signature_after_sign
+            headers[u'Authorization'] = ', '.join([k+'="'+v+'"' for k, v in old_parsed.items()])
+            return None, headers, None
+
+        patcher = mock.patch.object(requests.auth.Client, "sign", mocked_sign)
+        patcher.start()
+        self.addCleanup(patcher.stop)
+
+    def test_lti_constructor(self):
+        """
+        Makes sure that all parameters extracted.
+        """
+        self.runtime.render_template = lambda template, context: context
+        generated_context = self.item_module.get_html()
+        expected_context = {
+            'input_fields': self.correct_headers,
+            'element_class': self.item_module.location.category,
+            'element_id': self.item_module.location.html_id(),
+            'launch_url': '',  # default value
+        }
+        self.assertDictEqual(generated_context, expected_context)
diff --git a/lms/templates/lti.html b/lms/templates/lti.html
new file mode 100644
index 0000000000000000000000000000000000000000..3d97c8d808974cb2a95778d029701df5afb62840
--- /dev/null
+++ b/lms/templates/lti.html
@@ -0,0 +1,34 @@
+<div id="${element_id}" class="${element_class}">
+
+    ## This form will be hidden. Once available on the client, the LTI
+    ## module JavaScript will trigget a "submit" on the form, and the
+    ## result will be rendered to the below iFrame.
+    <form
+        action="${launch_url}"
+        name="ltiLaunchForm"
+        class="ltiLaunchForm"
+        method="post"
+        target="ltiLaunchFrame"
+        encType="application/x-www-form-urlencoded"
+    >
+
+        % for param_name, param_value in input_fields.items():
+            <input name="${param_name}" value="${param_value}" />
+        %endfor
+
+        <input type="submit" value="Press to Launch" />
+    </form>
+
+    <h3 class="error_message">
+        Please provide launch_url. Click "Edit", and fill in the
+        required fields.
+    </h3>
+
+    ## The result of the form submit will be rendered here.
+    <iframe
+        name="ltiLaunchFrame"
+        class="ltiLaunchFrame"
+        src=""
+    ></iframe>
+
+</div>