diff --git a/authserv/app_common.py b/authserv/app_common.py
index f10a66b81d8fd59f6ef735cb6f7c5113c16d844a..22f2a600b5f2a588cdbc414c50210d57b5659f82 100644
--- a/authserv/app_common.py
+++ b/authserv/app_common.py
@@ -3,7 +3,7 @@ from authserv import auth
 from authserv import protocol
 from authserv.ratelimit import *
 
-_blacklisted = (protocol.ERR_AUTHENTICATION_FAILURE, None)
+_blacklisted = (protocol.ERR_AUTHENTICATION_FAILURE, 'blacklisted', None)
 
 
 @blacklist_on_auth_failure(key_from_args(0), count=5, period=600, ttl=43200,
@@ -13,6 +13,6 @@ _blacklisted = (protocol.ERR_AUTHENTICATION_FAILURE, None)
 def do_auth(username, service, shard, password, otp_token, source_ip):
     user = current_app.userdb.get_user(username, service, shard)
     if not user:
-        return _blacklisted
-    return (auth.authenticate(user, service, password, otp_token),
-            user.get_shard())
+        return protocol.ERR_AUTHENTICATION_FAILURE, 'user does not exist', None
+    result, errmsg = auth.authenticate(user, service, password, otp_token)
+    return (result, errmsg, user.get_shard())
diff --git a/authserv/app_main.py b/authserv/app_main.py
index 5731361e37877c64d965907572d0598688a30346..3ee6df664d174463b651bd637067c638a17886e9 100644
--- a/authserv/app_main.py
+++ b/authserv/app_main.py
@@ -34,14 +34,16 @@ def api_auth():
         abort(400)
 
     try:
-        result, _ = do_auth(username, service, shard, password, otp_token, source_ip)
+        result, errmsg, unused_shard = do_auth(
+            username, service, shard, password, otp_token, source_ip)
     except Exception, e:
         app.logger.exception('Unexpected exception in authenticate()')
         abort(500)
 
     app.logger.info(
-        'AUTH %s %s otp=%s %s',
-        username, service, otp_token and 'y' or 'n', result)
+        'AUTH %s %s %s otp=%s%s',
+        username, service, result, otp_token and 'y' or 'n',
+        (' err=%s' % errmsg) if errmsg else '')
 
     response = make_response(result)
     response.headers['Cache-Control'] = 'no-cache'
diff --git a/authserv/app_nginx.py b/authserv/app_nginx.py
index 9661823865211ae4e0d3976fe075473faa37c4cc..36429616b92d5a16cc36b865393cce32e3b79c97 100644
--- a/authserv/app_nginx.py
+++ b/authserv/app_nginx.py
@@ -31,15 +31,16 @@ def do_nginx_http_auth():
         n_attempt = 1
 
     try:
-        auth_status, shard = do_auth(
+        auth_status, errmsg, shard = do_auth(
             username, service, None, password, None, source_ip)
     except Exception, e:
         app.logger.exception('Unexpected exception in authenticate()')
         abort(500)
 
     app.logger.info(
-        'NGINX_AUTH %s %s %s shard=%s',
-        username, service, auth_status, shard)
+        'NGINX_AUTH %s %s %s shard=%s%s',
+        username, service, auth_status, shard,
+        (' err=%s' % errmsg) if errmsg else '')
 
     response = make_response('')
     if auth_status == 'OK':
diff --git a/authserv/auth.py b/authserv/auth.py
index 87c0349fd5bf00fe296dfe456c46db8933269e56..d1dd9f327ef9835ca501c7f7e50cea5bcd24e105 100644
--- a/authserv/auth.py
+++ b/authserv/auth.py
@@ -6,16 +6,16 @@ from authserv import protocol
 
 def _check_main_password(userpw, password):
     if safe_str_cmp(crypt.crypt(password, userpw), userpw):
-        return protocol.OK
+        return protocol.OK, None
     else:
-        return protocol.ERR_AUTHENTICATION_FAILURE
+        return protocol.ERR_AUTHENTICATION_FAILURE, 'bad password'
 
 
 def _check_app_specific_password(asps, password):
     for app_pw in asps:
         if safe_str_cmp(crypt.crypt(password, app_pw), app_pw):
-            return protocol.OK
-    return protocol.ERR_AUTHENTICATION_FAILURE
+            return protocol.OK, None
+    return protocol.ERR_AUTHENTICATION_FAILURE, 'bad app-specific password'
 
 
 def _check_otp(totp_key, token):
@@ -32,23 +32,20 @@ def _check_otp(totp_key, token):
 
 def authenticate(user, service, password, otp_token):
     if not password:
-        return protocol.ERR_AUTHENTICATION_FAILURE
+        return protocol.ERR_AUTHENTICATION_FAILURE, 'empty password'
 
     if isinstance(password, unicode):
         password = password.encode('utf-8')
 
     if user.app_specific_passwords_enabled():
-        if _check_app_specific_password(user.get_app_specific_passwords(service),
-                                        password) == protocol.OK:
-            return protocol.OK
-        else:
-            return protocol.ERR_AUTHENTICATION_FAILURE
+        return _check_app_specific_password(
+            user.get_app_specific_passwords(service), password)
 
     if user.otp_enabled():
         if not otp_token:
-            return protocol.ERR_OTP_REQUIRED
+            return protocol.ERR_OTP_REQUIRED, 'otp required'
         if _check_otp(user.get_totp_key(), otp_token) != protocol.OK:
-            return protocol.ERR_AUTHENTICATION_FAILURE
+            return protocol.ERR_AUTHENTICATION_FAILURE, 'bad otp token'
         return _check_main_password(user.get_password(), password)
 
     return _check_main_password(user.get_password(), password)
diff --git a/authserv/test/test_app_nginx.py b/authserv/test/test_app_nginx.py
index 0f233becddaedc3d08f607028ca9f3147d1a1a03..089f9cfe819a8e2d23f6d8f992b225feb4a9c3e0 100644
--- a/authserv/test/test_app_nginx.py
+++ b/authserv/test/test_app_nginx.py
@@ -1,3 +1,4 @@
+import mock
 from authserv.test import *
 from authserv.ratelimit import *
 from authserv import protocol
@@ -24,16 +25,17 @@ class ServerTest(unittest.TestCase):
         self.app = app.test_client()
 
     def test_nginx_http_auth_ok(self):
-        response = self.app.get(
-            '/', headers={
-                'Auth-User': 'user',
-                'Auth-Pass': 'pass',
-                'Client-IP': '127.0.0.1',
-                'Auth-Protocol': 'imap',
-                'Auth-Login-Attempt': '1',
-            })
-        self.assertEquals(200, response.status_code)
-        self.assertEquals('OK', response.headers['Auth-Status'])
+        with mock.patch('socket.gethostbyname', return_value='127.0.0.1'):
+            response = self.app.get(
+                '/', headers={
+                    'Auth-User': 'user',
+                    'Auth-Pass': 'pass',
+                    'Client-IP': '127.0.0.1',
+                    'Auth-Protocol': 'imap',
+                    'Auth-Login-Attempt': '1',
+                })
+            self.assertEquals(200, response.status_code)
+            self.assertEquals('OK', response.headers['Auth-Status'])
 
     def test_nginx_http_auth_fail(self):
         response = self.app.get(
diff --git a/authserv/test/test_auth.py b/authserv/test/test_auth.py
index 184180b25ce318affd48401cce01b8fe7f89f25f..22c3c9b755291e33df2957c7aebb5fef9fd7e1dd 100644
--- a/authserv/test/test_auth.py
+++ b/authserv/test/test_auth.py
@@ -10,22 +10,22 @@ class AuthTest(unittest.TestCase):
         u = FakeUser('user', 'pass')
         self.assertEquals(
             protocol.OK,
-            authenticate(u, 'svc', 'pass', None))
+            authenticate(u, 'svc', 'pass', None)[0])
 
     def test_main_password_fail(self):
         u = FakeUser('user', 'pass')
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'svc', 'badpass', None))
+            authenticate(u, 'svc', 'badpass', None)[0])
 
     def test_require_otp(self):
         u = FakeUser('user', 'pass', otp_key=1234)
         self.assertEquals(
             protocol.ERR_OTP_REQUIRED,
-            authenticate(u, 'svc', 'pass', None))
+            authenticate(u, 'svc', 'pass', None)[0])
         self.assertEquals(
             protocol.ERR_OTP_REQUIRED,
-            authenticate(u, 'svc', 'badpass', None))
+            authenticate(u, 'svc', 'badpass', None)[0])
 
     def test_otp_ok(self):
         secret = '089421'
@@ -34,30 +34,30 @@ class AuthTest(unittest.TestCase):
         u = FakeUser('user', 'pass', otp_key=secret)
         self.assertEquals(
             protocol.OK,
-            authenticate(u, 'svc', 'pass', str(token)))
+            authenticate(u, 'svc', 'pass', str(token))[0])
 
     def test_otp_fail(self):
         u = FakeUser('user', 'pass', otp_key='123456')
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'svc', 'pass', '123456'))
+            authenticate(u, 'svc', 'pass', '123456')[0])
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'svc', 'pass', 'malformed otp token!'))            
+            authenticate(u, 'svc', 'pass', 'malformed otp token!')[0])
 
     def test_app_specific_password_ok(self):
         u = FakeUser('user', 'pass', asps=[
                 ('svc', crypt.crypt('app-specific', 'zz'))])
         self.assertEquals(
             protocol.OK,
-            authenticate(u, 'svc', 'app-specific', None))
+            authenticate(u, 'svc', 'app-specific', None)[0])
 
     def test_app_specific_password_fail(self):
         u = FakeUser('user', 'pass', asps=[
                 ('svc', crypt.crypt('app-specific', 'zz'))])
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'svc', 'badpass', None))
+            authenticate(u, 'svc', 'badpass', None)[0])
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'svc2', 'app-specific', None))
+            authenticate(u, 'svc2', 'app-specific', None)[0])
diff --git a/authserv/test/test_auth_ldap.py b/authserv/test/test_auth_ldap.py
index e491b1843fc13cbb20a4e1bc5a522b518a2ab642..8ad9a6371d3a01ed5b9910b9bf1b0cd079cd3dae 100644
--- a/authserv/test/test_auth_ldap.py
+++ b/authserv/test/test_auth_ldap.py
@@ -63,14 +63,14 @@ class LdapAuthTest(LdapAuthTestBase):
         self.assertTrue(u)
         self.assertEquals(
             protocol.OK,
-            authenticate(u, 'mail', 'password', None))
+            authenticate(u, 'mail', 'password', None)[0])
 
     def test_auth_password_fail(self):
         u = self.userdb.get_user('test@investici.org', 'mail', -1)
         self.assertTrue(u)
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'mail', 'wrong password', None))
+            authenticate(u, 'mail', 'wrong password', None)[0])
 
 
 class LdapOtpTest(LdapAuthTestBase):
@@ -84,14 +84,14 @@ class LdapOtpTest(LdapAuthTestBase):
         self.assertTrue(u)
         self.assertEquals(
             protocol.ERR_OTP_REQUIRED,
-            authenticate(u, 'account', 'password', None))
+            authenticate(u, 'account', 'password', None)[0])
 
     def test_auth_bad_password_requires_otp(self):
         u = self.userdb.get_user('test@investici.org', 'account', -1)
         self.assertTrue(u)
         self.assertEquals(
             protocol.ERR_OTP_REQUIRED,
-            authenticate(u, 'account', 'wrong password', None))
+            authenticate(u, 'account', 'wrong password', None)[0])
 
     def test_auth_otp_ok(self):
         u = self.userdb.get_user('test@investici.org', 'account', -1)
@@ -100,7 +100,7 @@ class LdapOtpTest(LdapAuthTestBase):
         token = totp(secret, format='dec6', period=30)
         self.assertEquals(
             protocol.OK,
-            authenticate(u, 'account', 'password', str(token)))
+            authenticate(u, 'account', 'password', str(token))[0])
 
     def test_auth_otp_ok_bad_password(self):
         u = self.userdb.get_user('test@investici.org', 'account', -1)
@@ -109,14 +109,14 @@ class LdapOtpTest(LdapAuthTestBase):
         token = totp(secret, format='dec6', period=30)
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'account', 'wrong password', str(token)))
+            authenticate(u, 'account', 'wrong password', str(token))[0])
 
     def test_auth_bad_otp(self):
         u = self.userdb.get_user('test@investici.org', 'account', -1)
         self.assertTrue(u)
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'account', 'password', '123456'))
+            authenticate(u, 'account', 'password', '123456')[0])
 
 
 class LdapASPTest(LdapAuthTestBase):
@@ -130,18 +130,18 @@ class LdapASPTest(LdapAuthTestBase):
         self.assertTrue(u)
         self.assertEquals(
             protocol.OK,
-            authenticate(u, 'mail', 'veryspecificpassword', None))
+            authenticate(u, 'mail', 'veryspecificpassword', None)[0])
 
     def test_plain_password_fails(self):
         u = self.userdb.get_user('test@investici.org', 'mail', -1)
         self.assertTrue(u)
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'mail', 'password', None))
+            authenticate(u, 'mail', 'password', None)[0])
 
     def test_plain_password_and_otp_fails(self):
         u = self.userdb.get_user('test@investici.org', 'mail', -1)
         self.assertTrue(u)
         self.assertEquals(
             protocol.ERR_AUTHENTICATION_FAILURE,
-            authenticate(u, 'mail', 'password', '123456'))
+            authenticate(u, 'mail', 'password', '123456')[0])