diff --git a/include/opendht/node.h b/include/opendht/node.h
index 5ecb9a61e1e4853e2e01605840a26a409f79db99..14e8ae7c1929243ac9c026a5b3365680a5e9a3dc 100644
--- a/include/opendht/node.h
+++ b/include/opendht/node.h
@@ -50,6 +50,18 @@ struct Node {
     std::string getAddrStr() const {
         return addr.toString();
     }
+
+    /**
+     * Makes notice about an additionnal authentication error with this node. Up
+     * to MAX_AUTH_ERRORS errors are accepted in order to let the node recover.
+     * Upon this limit, the node expires.
+     */
+    void authError() {
+        if (++auth_errors > MAX_AUTH_ERRORS)
+            setExpired();
+    }
+    void authSuccess() { auth_errors = 0; }
+
     bool isExpired() const { return expired_; }
     bool isGood(time_point now) const;
     bool isPendingMessage() const;
@@ -83,7 +95,11 @@ struct Node {
     static constexpr const std::chrono::seconds MAX_RESPONSE_TIME {1};
 
 private:
+    /* Number of times we accept authentication errors from this node. */
+    static const constexpr unsigned MAX_AUTH_ERRORS {3};
+
     std::list<std::weak_ptr<Request>> requests_ {};
+    unsigned auth_errors {0};
     bool expired_ {false};
 
     void clearPendingQueue() {
diff --git a/src/dht.cpp b/src/dht.cpp
index e7f9f803dba0c7f836778a567fa0ade9b25a4881..c1550185a11af508805935f093d3a096f5d5ac38 100644
--- a/src/dht.cpp
+++ b/src/dht.cpp
@@ -408,9 +408,9 @@ struct Dht::SearchNode {
                                                           or not gs->second or not gs->second->pending()))
             return time_point::min();
         return ((gs != getStatus.cend() and gs->second and gs->second->pending())
-                or ack == acked.cend() or not ack->second or ack->second->pending()) ?
-                    time_point::max() :
-                    ack->second->reply_time + type.expiration - REANNOUNCE_MARGIN;
+                or ack == acked.cend() or not ack->second or ack->second->pending())
+                ? time_point::max()
+                : ack->second->reply_time + type.expiration - REANNOUNCE_MARGIN;
     }
 
     /**
@@ -3000,6 +3000,7 @@ void
 Dht::onError(std::shared_ptr<Request> req, DhtProtocolException e) {
     if (e.getCode() == DhtProtocolException::UNAUTHORIZED) {
         DHT_LOG.ERR("[node %s] token flush", req->node->toString().c_str());
+        req->node->authError();
         network_engine.cancelRequest(req);
         for (auto& srp : req->node->getFamily() == AF_INET ? searches4 : searches6) {
             auto& sr = srp.second;
diff --git a/src/network_engine.cpp b/src/network_engine.cpp
index f0e8c4012392825d605fed68f7db159760bb2c72..4ada3962fb26832344bfcb82898abff14b692a82 100644
--- a/src/network_engine.cpp
+++ b/src/network_engine.cpp
@@ -377,6 +377,9 @@ NetworkEngine::processMessage(const uint8_t *buf, size_t buflen, const SockAddr&
             break;
         }
         case MessageType::Reply:
+            if (msg.type == MessageType::AnnounceValue or msg.type == MessageType::Listen)
+                req->node->authSuccess();
+
             // erase before calling callback to make sure iterator is still valid
             if (not req->persistent)
                 requests.erase(reqp);