diff --git a/common/djangoapps/third_party_auth/tests/specs/base.py b/common/djangoapps/third_party_auth/tests/specs/base.py
index 73521de0f5617730b26c1641bb2d6582a2961fc3..c2af79b603ab2325aa1871ae6730ee6c61aeec53 100644
--- a/common/djangoapps/third_party_auth/tests/specs/base.py
+++ b/common/djangoapps/third_party_auth/tests/specs/base.py
@@ -130,11 +130,10 @@ class HelperMixin(object):
 
     def assert_json_failure_response_is_missing_social_auth(self, response):
         """Asserts failure on /login for missing social auth looks right."""
-        self.assertContains(
-            response,
-            u"successfully signed in to your %s account, but this account isn't linked" % self.provider.name,
-            status_code=403,
-        )
+        self.assertEqual(403, response.status_code)
+        payload = json.loads(response.content.decode('utf-8'))
+        self.assertFalse(payload.get('success'))
+        self.assertEqual(payload.get('error_code'), 'third-party-auth-with-no-linked-account')
 
     def assert_json_failure_response_is_username_collision(self, response):
         """Asserts the json response indicates a username collision."""
diff --git a/lms/static/js/student_account/views/LoginView.js b/lms/static/js/student_account/views/LoginView.js
index 477aa7d1973c1396e6360418542b00362aa05ab0..7fe1f004ec6533b68f96388541f09c381e0daac0 100644
--- a/lms/static/js/student_account/views/LoginView.js
+++ b/lms/static/js/student_account/views/LoginView.js
@@ -189,6 +189,51 @@
             },
 
             saveError: function(error) {
+                if (error.responseJSON !== undefined) {
+                    this.saveErrorWithoutShim(error);
+                } else {
+                    this.saveErrorWithShim(error);
+                }
+            },
+
+            saveErrorWithoutShim: function(error) {
+                var errorCode;
+                var msg;
+                if (error.status === 0) {
+                    msg = gettext('An error has occurred. Check your Internet connection and try again.');
+                } else if (error.status === 500) {
+                    msg = gettext('An error has occurred. Try refreshing the page, or check your Internet connection.'); // eslint-disable-line max-len
+                } else if (error.responseJSON !== undefined) {
+                    msg = error.responseJSON.value;
+                    errorCode = error.responseJSON.error_code;
+                } else {
+                    msg = gettext('An unexpected error has occurred.');
+                }
+
+                this.errors = [
+                    StringUtils.interpolate(
+                        '<li>{msg}</li>', {
+                            msg: msg
+                        }
+                    )
+                ];
+                this.clearPasswordResetSuccess();
+
+                /* If the user successfully authenticated with a third-party provider, but they haven't
+                 * linked the accounts, instruct the user on how to link the accounts.
+                 */
+                if (errorCode === 'third-party-auth-with-no-linked-account' && this.currentProvider) {
+                    if (!this.hideAuthWarnings) {
+                        this.clearFormErrors();
+                        this.renderThirdPartyAuthWarning();
+                    }
+                } else {
+                    this.renderErrors(this.defaultFormErrorsTitle, this.errors);
+                }
+                this.toggleDisableButton(false);
+            },
+
+            saveErrorWithShim: function(error) {
                 var msg = error.responseText;
                 if (error.status === 0) {
                     msg = gettext('An error has occurred. Check your Internet connection and try again.');
@@ -215,7 +260,7 @@
                  this.currentProvider) {
                     if (!this.hideAuthWarnings) {
                         this.clearFormErrors();
-                        this.renderAuthWarning();
+                        this.renderThirdPartyAuthWarning();
                     }
                 } else {
                     this.renderErrors(this.defaultFormErrorsTitle, this.errors);
@@ -223,7 +268,7 @@
                 this.toggleDisableButton(false);
             },
 
-            renderAuthWarning: function() {
+            renderThirdPartyAuthWarning: function() {
                 var message = _.sprintf(
                     gettext('You have successfully signed into %(currentProvider)s, but your %(currentProvider)s' +
                             ' account does not have a linked %(platformName)s account. To link your accounts,' +
diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py
index 268b5f5365b3f5895d1c104fb3818981837368b0..32a96e6c5c0490698fd6e3b901c55721db032ef8 100644
--- a/openedx/core/djangoapps/user_authn/views/login.py
+++ b/openedx/core/djangoapps/user_authn/views/login.py
@@ -400,6 +400,7 @@ def login_user(request):
         response = set_logged_in_cookies(request, response, possibly_authenticated_user)
         set_custom_metric('login_user_auth_failed_error', False)
         set_custom_metric('login_user_response_status', response.status_code)
+        set_custom_metric('login_user_redirect_url', redirect_url)
         return response
     except AuthFailedError as error:
         log.exception(error.get_response())
@@ -483,10 +484,15 @@ def _parse_analytics_param_for_course_id(request):
     modified_request = request.POST.copy()
     if isinstance(request, HttpRequest):
         # Works for an HttpRequest but not a rest_framework.request.Request.
+        # Note: This case seems to be used for tests only.
         request.POST = modified_request
+        set_custom_metric('login_user_request_type', 'django')
     else:
         # The request must be a rest_framework.request.Request.
+        # Note: Only DRF seems to be used in Production.
         request._data = modified_request  # pylint: disable=protected-access
+        set_custom_metric('login_user_request_type', 'drf')
+
     # Include the course ID if it's specified in the analytics info
     # so it can be included in analytics events.
     if "analytics" in modified_request:
@@ -566,6 +572,8 @@ def shim_student_view(view_func, check_logged_in=False):
             msg = response_dict.get("value", u"")
             success = response_dict.get("success")
             set_custom_metric('shim_original_response_is_json', True)
+            set_custom_metric('shim_original_redirect_url', response_dict.get("redirect_url"))
+            set_custom_metric('shim_original_redirect', response_dict.get("redirect"))
         except (ValueError, TypeError):
             msg = response.content
             success = True
diff --git a/openedx/core/djangoapps/user_authn/views/login_form.py b/openedx/core/djangoapps/user_authn/views/login_form.py
index 6397103850b4decc070f50cc2c87166f60f4a4ed..faeab3ef952a84323d88313004bb7d8a8a9def21 100644
--- a/openedx/core/djangoapps/user_authn/views/login_form.py
+++ b/openedx/core/djangoapps/user_authn/views/login_form.py
@@ -77,6 +77,20 @@ def _apply_third_party_auth_overrides(request, form_desc):
                 )
 
 
+# .. toggle_name: FEATURES[ENABLE_LOGIN_POST_WITHOUT_SHIM]
+# .. toggle_implementation: DjangoSetting
+# .. toggle_default: False
+# .. toggle_description: Toggle for enabling login post without shim_student_view (using `login_api`).
+# .. toggle_category: n/a
+# .. toggle_use_cases: incremental_release
+# .. toggle_creation_date: 2019-12-10
+# .. toggle_expiration_date: 2020-06-01
+# .. toggle_warnings: n/a
+# .. toggle_tickets: ARCH-1253
+# .. toggle_status: supported
+ENABLE_LOGIN_POST_WITHOUT_SHIM = 'ENABLE_LOGIN_POST_WITHOUT_SHIM'
+
+
 def get_login_session_form(request):
     """Return a description of the login form.
 
@@ -91,7 +105,12 @@ def get_login_session_form(request):
         HttpResponse
 
     """
-    form_desc = FormDescription("post", reverse("user_api_login_session"))
+    if settings.FEATURES.get(ENABLE_LOGIN_POST_WITHOUT_SHIM):
+        submit_url = reverse("login_api")
+    else:
+        submit_url = reverse("user_api_login_session")
+
+    form_desc = FormDescription("post", submit_url)
     _apply_third_party_auth_overrides(request, form_desc)
 
     # Translators: This label appears above a field on the login form
diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_login.py b/openedx/core/djangoapps/user_authn/views/tests/test_login.py
index 49e6d84cda0b3d42e8bb579886932a83a1981514..df33328efa7e0abbdc20f67e6fc913d464f68f3d 100644
--- a/openedx/core/djangoapps/user_authn/views/tests/test_login.py
+++ b/openedx/core/djangoapps/user_authn/views/tests/test_login.py
@@ -34,6 +34,7 @@ from openedx.core.djangoapps.user_authn.views.login import (
     AllowedAuthUser,
     ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY
 )
+from openedx.core.djangoapps.user_authn.views.login_form import ENABLE_LOGIN_POST_WITHOUT_SHIM
 from openedx.core.djangoapps.user_authn.tests.utils import setup_login_oauth_client
 from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
 from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
@@ -661,15 +662,26 @@ class LoginSessionViewTest(ApiTestCase):
         response = self.client.patch(self.url)
         self.assertHttpMethodNotAllowed(response)
 
-    def test_login_form(self):
-        # Retrieve the login form
-        response = self.client.get(self.url, content_type="application/json")
-        self.assertHttpOK(response)
+    @ddt.data(
+        {ENABLE_LOGIN_POST_WITHOUT_SHIM: True},
+        {ENABLE_LOGIN_POST_WITHOUT_SHIM: False},
+        {},
+    )
+    def test_login_form(self, features_setting):
+        with patch.dict("django.conf.settings.FEATURES", features_setting):
+            # Retrieve the login form
+            response = self.client.get(self.url, content_type="application/json")
+            self.assertHttpOK(response)
+
+        if ENABLE_LOGIN_POST_WITHOUT_SHIM in features_setting and features_setting[ENABLE_LOGIN_POST_WITHOUT_SHIM]:
+            submit_url = reverse("login_api")
+        else:
+            submit_url = reverse("user_api_login_session")
 
         # Verify that the form description matches what we expect
         form_desc = json.loads(response.content.decode('utf-8'))
         self.assertEqual(form_desc["method"], "post")
-        self.assertEqual(form_desc["submit_url"], self.url)
+        self.assertEqual(form_desc["submit_url"], submit_url)
         self.assertEqual(form_desc["fields"], [
             {
                 "name": "email",