diff --git a/hooks/horizon_utils.py b/hooks/horizon_utils.py
index 1c7c4722a4f4cef149eec5f1d43d41bf6acba083..6be0db385d91d2a0b029b33ab1be664617bd035c 100644
--- a/hooks/horizon_utils.py
+++ b/hooks/horizon_utils.py
@@ -407,5 +407,10 @@ def _pause_resume_helper(f, configs):
 
 
 def db_migration():
-    cmd = ['/usr/share/openstack-dashboard/manage.py', 'syncdb', '--noinput']
-    subprocess.call(cmd)
+    if cmp_pkgrevno('python-django', '1.9') >= 0:
+        # syncdb was removed in django 1.9
+        subcommand = 'migrate'
+    else:
+        subcommand = 'syncdb'
+    cmd = ['/usr/share/openstack-dashboard/manage.py', subcommand, '--noinput']
+    subprocess.check_call(cmd)
diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py
index 0e62069afd8d1afbcbe03bd100286895881db242..78c8f0e5c298767233907bf87c752ef5eb27cedc 100644
--- a/tests/basic_deployment.py
+++ b/tests/basic_deployment.py
@@ -15,6 +15,7 @@
 # limitations under the License.
 
 import amulet
+import requests
 import urllib2
 import time
 
@@ -79,6 +80,7 @@ class OpenstackDashboardBasicDeployment(OpenStackAmuletDeployment):
         relations = {
             'openstack-dashboard:identity-service':
             'keystone:identity-service',
+            'openstack-dashboard:shared-db': 'percona-cluster:shared-db',
             'keystone:shared-db': 'percona-cluster:shared-db',
         }
         super(OpenstackDashboardBasicDeployment, self)._add_relations(
@@ -86,7 +88,9 @@ class OpenstackDashboardBasicDeployment(OpenStackAmuletDeployment):
 
     def _configure_services(self):
         """Configure all of the services."""
-        horizon_config = {}
+        horizon_config = {
+            'debug': 'yes',
+        }
         keystone_config = {
             'admin-password': 'openstack',
             'admin-token': 'ubuntutesting',
@@ -235,6 +239,62 @@ class OpenstackDashboardBasicDeployment(OpenStackAmuletDeployment):
             msg = "Dashboard frontpage check failed"
             amulet.raise_status(amulet.FAIL, msg=msg)
 
+    def test_401_authenticate(self):
+        """Validate that authentication succeeds when client logs in through
+        the OpenStack Dashboard"""
+
+        u.log.debug('Checking authentication through dashboard...')
+        unit = self.openstack_dashboard_sentry
+        dashboard_relation = unit.relation('identity-service',
+                                           'keystone:identity-service')
+        dashboard_ip = dashboard_relation['private-address']
+        url = 'http://{}/horizon/auth/login/'.format(dashboard_ip)
+
+        api_version = None
+        if self._get_openstack_release() < self.xenial_queens:
+            api_version = 2
+
+        region = u.get_keystone_endpoint(
+            self.keystone_sentry.info['public-address'], api_version)
+
+        # start session, get csrftoken
+        client = requests.session()
+        client.get(url)
+        response = client.get(url)
+
+        if 'csrftoken' in client.cookies:
+            csrftoken = client.cookies['csrftoken']
+
+        # build and send post request
+        auth = {
+            'domain': 'admin_domain',
+            'username': 'admin',
+            'password': 'openstack',
+            'csrfmiddlewaretoken': csrftoken,
+            'next': '/horizon/',
+            'region': region,
+        }
+        if api_version == 2:
+            del auth['domain']
+
+        u.log.debug('POST data: "{}"'.format(auth))
+        response = client.post(url, data=auth, headers={'Referer': url})
+
+        if self._get_openstack_release() == self.trusty_icehouse:
+            # icehouse horizon does not operate properly without the compute
+            # service present in the keystone catalog.  However, checking for
+            # presence of the following text is sufficient to determine whether
+            # authentication succeeded or not
+            expect = 'ServiceCatalogException at /admin/'
+        else:
+            expect = 'Projects - OpenStack Dashboard'
+
+        if expect not in response.text:
+            msg = 'FAILURE code={} text="{}"'.format(response, response.text)
+            amulet.raise_status(amulet.FAIL, msg=msg)
+
+        u.log.debug('OK')
+
     def test_404_connection(self):
         """Verify the apache status module gets disabled when
         hardening apache."""
diff --git a/unit_tests/test_horizon_utils.py b/unit_tests/test_horizon_utils.py
index 14d2fdfbb5d53b1f20938e3bed8c6da7674a40db..dea1229c605e15cd902f153811090dd10debc334 100644
--- a/unit_tests/test_horizon_utils.py
+++ b/unit_tests/test_horizon_utils.py
@@ -229,3 +229,19 @@ class TestHorizonUtils(CharmTestCase):
             asf.assert_called_once_with('some-config')
             # ports=None whilst port checks are disabled.
             f.assert_called_once_with('assessor', services='s1', ports=None)
+
+    @patch('subprocess.check_call')
+    def test_db_migration(self, mock_subprocess):
+        self.cmp_pkgrevno.return_value = -1
+        horizon_utils.db_migration()
+        mock_subprocess.assert_called_with(
+            ['/usr/share/openstack-dashboard/manage.py',
+             'syncdb', '--noinput'])
+
+    @patch('subprocess.check_call')
+    def test_db_migration_bionic_and_beyond(self, mock_subprocess):
+        self.cmp_pkgrevno.return_value = 0
+        horizon_utils.db_migration()
+        mock_subprocess.assert_called_with(
+            ['/usr/share/openstack-dashboard/manage.py',
+             'migrate', '--noinput'])