diff --git a/server/authserver.go b/server/authserver.go
index c22f6583da9aa839276363142bbcc3b6ccab4e9c..c480142d90e9e8d4fde49b119ae474062f45d596 100644
--- a/server/authserver.go
+++ b/server/authserver.go
@@ -389,7 +389,14 @@ func (s *Server) Authenticate(ctx context.Context, req *auth.Request) *auth.Resp
 		return newError()
 	}
 
-	resp := s.authenticateUser(req, serviceConfig, user)
+	resp, err := s.authenticateUser(req, serviceConfig, user)
+	if err != nil {
+		resp = newError()
+		log.Printf("auth: user=%s status=%s error=%s", req.Username, resp.Status.String(), err)
+	} else {
+		// Log the request and response.
+		log.Printf("auth: user=%s status=%s", req.Username, resp.Status.String())
+	}
 
 	// Notify blacklists of the result.
 	serviceConfig.notifyBlacklists(user, req, resp)
@@ -397,19 +404,23 @@ func (s *Server) Authenticate(ctx context.Context, req *auth.Request) *auth.Resp
 	return resp
 }
 
-func (s *Server) authenticateUser(req *auth.Request, serviceConfig *ServiceConfig, user *User) *auth.Response {
+// Authenticate a user. Returning an error should result in an
+// AuthResponse with StatusError.
+func (s *Server) authenticateUser(req *auth.Request, serviceConfig *ServiceConfig, user *User) (resp *auth.Response, err error) {
 	// Verify different credentials depending on whether the user
 	// has 2FA enabled or not, and on whether the service itself
 	// supports challenge-response authentication.
-	var resp *auth.Response
 	if serviceConfig.Enforce2FA || user.Has2FA() {
 		if serviceConfig.ChallengeResponse {
-			resp = s.authenticateUserWith2FA(user, req)
+			resp, err = s.authenticateUserWith2FA(user, req)
 		} else {
-			resp = s.authenticateUserWithASP(user, req)
+			resp, err = s.authenticateUserWithASP(user, req)
 		}
 	} else {
-		resp = s.authenticateUserWithPassword(user, req)
+		resp, err = s.authenticateUserWithPassword(user, req)
+	}
+	if err != nil {
+		return
 	}
 
 	// Process the response through filters (device info checks,
@@ -425,34 +436,30 @@ func (s *Server) authenticateUser(req *auth.Request, serviceConfig *ServiceConfi
 	if resp.Status == auth.StatusOK {
 		resp.UserInfo = user.UserInfo()
 	}
-
-	return resp
+	return
 }
 
-func (s *Server) authenticateUserWithPassword(user *User, req *auth.Request) *auth.Response {
+func (s *Server) authenticateUserWithPassword(user *User, req *auth.Request) (*auth.Response, error) {
 	// Ok we only need to check the password here.
 	if checkPassword(req.Password, user.EncryptedPassword) {
-		return newOK()
+		return newOK(), nil
 	}
-	log.Printf("wrong password")
-	return newError()
+	return nil, errors.New("wrong password")
 }
 
-func (s *Server) authenticateUserWithASP(user *User, req *auth.Request) *auth.Response {
+func (s *Server) authenticateUserWithASP(user *User, req *auth.Request) (*auth.Response, error) {
 	for _, asp := range user.AppSpecificPasswords {
 		if asp.Service == req.Service && checkPassword(req.Password, asp.EncryptedPassword) {
-			return newOK()
+			return newOK(), nil
 		}
 	}
-	log.Printf("wrong app-specific password")
-	return newError()
+	return nil, errors.New("wrong app-specific password")
 }
 
-func (s *Server) authenticateUserWith2FA(user *User, req *auth.Request) *auth.Response {
+func (s *Server) authenticateUserWith2FA(user *User, req *auth.Request) (*auth.Response, error) {
 	// First of all verify the password.
 	if !checkPassword(req.Password, user.EncryptedPassword) {
-		log.Printf("wrong password")
-		return newError()
+		return nil, errors.New("wrong password")
 	}
 
 	// If the request contains one of the 2FA attributes, verify
@@ -462,16 +469,14 @@ func (s *Server) authenticateUserWith2FA(user *User, req *auth.Request) *auth.Re
 	switch {
 	case req.U2FResponse != nil:
 		if user.HasU2F() && s.checkU2F(user, req.U2FResponse) {
-			return newOK()
+			return newOK(), nil
 		}
-		log.Printf("bad U2F response")
-		return newError()
+		return nil, errors.New("bad U2F response")
 	case req.OTP != "":
 		if user.HasOTP() && checkOTP(req.OTP, user.TOTPSecret) {
-			return newOK()
+			return newOK(), nil
 		}
-		log.Printf("bad OTP")
-		return newError()
+		return nil, errors.New("bad OTP")
 	default:
 		resp := &auth.Response{
 			Status: auth.StatusInsufficientCredentials,
@@ -480,13 +485,13 @@ func (s *Server) authenticateUserWith2FA(user *User, req *auth.Request) *auth.Re
 			resp.TFAMethod = auth.TFAMethodU2F
 			signReq, err := s.u2fSignRequest(user, req.U2FAppID)
 			if err != nil {
-				return newError()
+				return nil, err
 			}
 			resp.U2FSignRequest = signReq
 		} else if user.HasOTP() {
 			resp.TFAMethod = auth.TFAMethodOTP
 		}
-		return resp
+		return resp, nil
 	}
 }
 
diff --git a/server/devices.go b/server/devices.go
index 4ccf3c7f191be41677ae0a2ccef8559442a5ab4e..8e29d00a79e04c472ed817b3ed3cfefff9bec7b6 100644
--- a/server/devices.go
+++ b/server/devices.go
@@ -11,12 +11,12 @@ import (
 )
 
 // Inject an interface for testing purposes.
-type userMetaDB interface {
+type checkDeviceClient interface {
 	CheckDevice(context.Context, string, *auth.DeviceInfo) (bool, error)
 }
 
 type deviceFilter struct {
-	client userMetaDB
+	client checkDeviceClient
 }
 
 var usermetadbTimeout = 3 * time.Second