Authentication failed от MongoDB, код ошибки 18, означает одно: сервер получил имя пользователя и пароль и не смог сопоставить их ни с одним пользователем. Это не значит, что сервер недоступен, порт закрыт или сломан TLS - такие проблемы проявляются раньше и выглядят иначе. Если учётные данные точно верны, причина почти всегда в одном из четырёх: пользователь находится не в той базе, которую запросил драйвер (authSource), пароль, который отправил драйвер, не совпадает с тем, что вы набрали (кодирование, кавычки, лишний перевод строки), используется не та строка подключения, о которой вы думаете, либо механизм или умолчание +srv изменили то, что уходит на сервер.
Самый быстрый путь к ответу - перестать гадать на стороне клиента, спросить сервер и затем вручную воспроизвести подключение в mongosh. В этой статье диагностика разобрана по порядку, а затем описаны ловушки каждого драйвера. О том, как вообще собирается URI - части, параметры, размеры пула - читайте в статье MongoDB connection strings; здесь предполагается, что строка у вас есть, но она не работает.
Сначала убедитесь, что это действительно ошибка аутентификации#
Удивительно много времени уходит на исправление паролей из-за ошибок, которые никакого отношения к паролям не имели. MongoDB отказывает по слоям, и у каждого слоя своё сообщение. Прежде чем что-либо менять, выясните, на каком слое вы остановились.
| Сообщение | Слой | Куда смотреть |
|---|---|---|
Authentication failed / code 18 | Учётные данные | Пользователь, база, пароль |
bad auth : Authentication failed | Учётные данные (Atlas) | Пользователь базы, а не ваш логин в Atlas |
ServerSelectionTimeoutError | Сеть или TLS | Хост, порт, firewall, tls |
ECONNREFUSED | Сеть | Адрес привязки, порт, запущена ли служба |
querySrv ENOTFOUND | DNS | +srv для хоста без SRV-записей |
requires authentication (code 13) | Нет учётных данных | В URI нет пользователя или не загружено окружение |
not authorized on shop to execute | Роли | Пользователь есть, но у него нет нужной роли |
Последние два стоит отделять от первого. command find requires authentication означает, что вы подключились анонимно: учётные данные в запрос не попали, обычно потому, что переменная окружения оказалась пустой и код откатился к mongodb://localhost:27017. not authorized означает, что аутентификация прошла, а у пользователя просто нет роли в этой базе. Ни то, ни другое не лечится сменой пароля; оба случая решаются проверкой того, что пользователю разрешено, или того, какой URI ваш процесс на самом деле загрузил.
Полезная привычка перед дальнейшей отладкой - вывести URI, который приложение использует на самом деле, с замаскированным паролем. Что-то вроде uri.replace(/:([^@/]+)@/, ":****@") в JavaScript покажет хост, порт, базу и параметры, не раскрывая секрет. Значительная доля случаев «Authentication failed при верном пароле» оказывается URI от staging в контейнере production или значением по умолчанию в коде, которое так и не переопределили.
Спросите сервер, что именно было не так#
Клиент получает намеренно расплывчатое сообщение, потому что фраза «такого пользователя нет» помогла бы атакующему. Журнал сервера расплывчатым не бывает. Если у вас есть доступ к журналу mongod, найдите строку о неудачной попытке; в MongoDB 4.4 и новее это структурированный JSON с сообщением Authentication failed и объектом attr, где указаны механизм, пользователь, база, в которой его искали, адрес клиента и настоящая причина.
{"msg":"Authentication failed","attr":{"mechanism":"SCRAM-SHA-256", "principalName":"shopapp","authenticationDatabase":"shop", "remote":"203.0.113.40:51522", "error":"UserNotFound: Could not find user \"shopapp\" for db \"shop\""}}Эта единственная строка отвечает на большую часть вопроса. Смотрите на три поля:
authenticationDatabase- база, которую запросил драйвер. Если тамshop, а пользователь создан вadmin, решение -authSource=admin, и пароль никогда не был проблемой.error, начинающийся сUserNotFound, означает, что пользователя с таким именем в этой базе нет. Либо имя написано с ошибкой, либо он находится в другой базе, либо вы обращаетесь не к тому серверу, на котором его создавали.errorс упоминанием ошибки SCRAM, напримерstoredKey mismatch, означает, что пользователь найден, а пароль не подошёл. Вот теперь, и только теперь, имеет смысл разбираться с самим паролем.
Точные формулировки и формат этих строк различаются между версиями, а старые серверы писали обычный текст, а не JSON, но те же три факта там есть. Если журнал сервера вам недоступен - управляемый сервис или чужой сервер - mongosh приведёт к тем же ответам, как описано ниже.
authSource и пользователь, который находится в другом месте#
Каждый пользователь MongoDB принадлежит одной базе. Параметр authSource в строке подключения указывает, в какой именно базе его искать; если он не задан, драйвер использует базу из пути URI и переходит к admin только тогда, когда путь пуст. Это умолчание - самая частая причина кода 18.
Классический случай: вам выдали административного пользователя, который живёт в admin, а вы пишете URI, указывающий на базу вашего приложения.
mongodb://root:pw@db.example.net:27017/shopДрайвер ищет root в shop, никого не находит и сообщает об ошибке. Добавьте ?authSource=admin, и те же учётные данные заработают. Обратная ошибка встречается так же часто: пользователь создан командой use shop перед db.createUser(...), а затем в URI подставлен authSource=admin, скопированный из руководства. В admin такого пользователя нет, поэтому правильный ответ - authSource=shop, либо пересоздать пользователя в admin с ролью, ограниченной базой shop, и больше об этом не думать.
Чтобы выяснить, где пользователь живёт на самом деле, подключитесь любой учётной записью, которая может просматривать пользователей, и спросите весь сервер:
db.adminCommand({ usersInfo: { forAllDBs: true } }).users .map(u => `${u.user} @ ${u.db}`)Каждая строка показывает имя пользователя и базу, которой он принадлежит. Два пользователя с одинаковым именем в двух базах - это совершенно разные учётные записи с разными паролями, чем объясняется иногда встречающееся «из одного приложения работает, из другого нет»: два приложения аутентифицируются под разными пользователями, которые просто носят одно имя.
Для одной базы можно также выполнить db.getSiblingDB("shop").getUsers(). Пустой массив значит, что в shop никого нет, и вопрос с authSource сразу закрыт.
Совсем не тот сервер
За UserNotFound для пользователя, которого вы точно создавали, чаще, чем принято признавать, стоит более скучное объяснение: клиент разговаривает не с тем сервером, на котором пользователь создан. Локальный mongod на вашем ноутбуке, слушающий 27017, охотно ответит на URI, предназначенный для удалённого хоста, если SSH-туннель пробрасывает тот же локальный порт или если файл .env указывает на localhost. Проверьте это через db.hostInfo().system.hostname после подключения или посмотрите адрес remote в журнале сервера. Если вы используете туннель, пробрасывайте его на другой локальный порт, например 27018, чтобы их нельзя было перепутать - синтаксис описан в статье SSH tunnels and port forwarding.
Пароль, который не совпадает с тем, что вы набрали#
Если журнал сервера говорит, что пользователь найден, а пароль не подошёл, считайте, что байты, которые дошли до сервера, отличаются от задуманных. Вот способы, которыми это происходит, примерно в порядке частоты.
Процентное кодирование: пропущенное или двойное
Любой из символов : / ? # [ ] @ % в пароле нужно закодировать в процентной форме внутри URI. Оставьте хотя бы один незакодированным, и драйвер либо отвергнет URI, либо прочитает часть пароля как хост. Обратная ошибка менее заметна: кодирование дважды. Пароль p@ss правильно превращается в p%40ss в URI; если эту закодированную строку пропустить через encodeURIComponent ещё раз, получится p%2540ss, и сервер получит буквальный текст p%40ss. Кодируйте ровно один раз, в том месте, где собирается URI, и никогда не храните уже закодированный пароль в переменной, которую потом закодирует что-то другое.
То же происходит и в обратную сторону, когда пароль передаётся отдельным параметром драйвера, а не в URI. Отдельные параметры не декодируются, поэтому пароль, скопированный из закодированного URI и вставленный в поле password, придёт с %40 внутри. В какой бы форме вы его ни передавали, отдельное поле принимает исходный пароль, а URI - закодированный.
Кавычки в оболочке
Если вставить URI в командную строку без кавычек, оболочка доберётся до него первой. Символ & между параметрами отправляет команду в фон и отбрасывает всё, что после него, так что authSource=admin тихо исчезает. Внутри двойных кавычек $ начинает переменную: пароль pa$word превращается в pa плюс то, во что раскроется $word, обычно в пустоту. В интерактивной сессии bash ! тоже может вызвать подстановку из истории.
$ mongosh 'mongodb://shopapp:pa$word@db.example.net:27017/shop?authSource=admin'Одинарные кавычки в bash и zsh передают всё буквально. PowerShell тоже раскрывает $ внутри двойных кавычек и использует обратный апостроф как символ экранирования, так что и там безопаснее одинарные кавычки. Ещё лучше вообще не указывать пароль и дать mongosh запросить его, - тогда оболочка полностью выпадает из проблемы.
Файлы окружения, секреты и завершающие переводы строки
Секрет, созданный командой echo "pw" > file, содержит перевод строки после пароля; printf '%s' "pw" его не добавляет. Прочитайте такой файл в переменную окружения или в секрет Kubernetes, и драйвер отправит pw\n, что не совпадёт. Некоторые загрузчики .env сохраняют окружающие кавычки как часть значения, некоторые убирают их, и поведение отличается между библиотеками и версиями. Docker Compose воспринимает $ в файле compose как подстановку переменной, поэтому буквальный знак доллара там нужно писать как $$.
Быстрая проверка - вывести длину пароля, который получил ваш процесс, и сравнить её с ожидаемой. Лишний один-два символа - это перевод строки или пара кавычек.
Root-пользователь Docker, который так и не пересоздали
Официальный образ mongo создаёт root-пользователя из MONGO_INITDB_ROOT_USERNAME и MONGO_INITDB_ROOT_PASSWORD в базе admin, и только если каталог данных при первом запуске пуст. Измените эти переменные позже, и ничего не произойдёт: существующий том по-прежнему хранит старого пользователя со старым паролем. Если вы сменили пароль в файле compose и теперь аутентификация не проходит, причина именно в этом. Либо смените пароль внутри базы через changeUserPassword, либо сознательно удалите том и начните заново - что уничтожит данные. Заметьте также, что MONGO_INITDB_DATABASE не создаёт пользователя в этой базе; root-пользователь по-прежнему живёт в admin, так что применим authSource=admin.
mongodb и mongodb+srv: что меняется для аутентификации#
Строка mongodb+srv:// заставляет драйвер искать список серверов в DNS. Он также читает TXT-запись на том же имени, и эта TXT-запись может задавать ровно три параметра: authSource, replicaSet и loadBalanced. Так управляемые сервисы вроде MongoDB Atlas делают так, что authSource=admin появляется без вашего участия. Если разобрать строку в стиле Atlas и переписать её как обычный список хостов mongodb://, эти умолчания теряются, и аутентификация не пройдёт, пока вы сами не вернёте authSource=admin. Параметры в URI переопределяют TXT-запись, поэтому вручную добавленный authSource всегда побеждает.
Другое умолчание +srv - TLS, он включён. Если перейти на mongodb://, не добавив tls=true, вы получите подключение, которое сервер отвергает, и проявится это как таймаут, а не как ошибка аутентификации.
Что касается Atlas, несколько моментов объясняют большинство сообщений «bad auth»:
- Пользователь в URI должен быть пользователем базы данных со страницы Database Access проекта, а не почтой и паролем, с которыми вы входите на сайт Atlas. Это не связанные между собой учётные записи.
- Только что созданному или изменённому пользователю базы может понадобиться немного времени, чтобы дойти до кластера; сразу после изменения одну повторную попытку стоит сделать, прежде чем начинать отладку.
- Адрес, отсутствующий в списке сетевого доступа, не вызывает
bad auth. Он вызывает таймаут выбора сервера, потому что соединение обрывается до начала аутентификации.
Когда люди ищут «mongodb get connection string», ответ зависит от того, где находится база. Atlas выдаёт строку в диалоге Connect кластера, в форме +srv. Автономный сервер, свой или арендованный, даёт вам хост, порт, пользователя и пароль, а mongodb://user:pass@host:port/db?authSource=... вы собираете сами. У автономного сервера нет SRV-записей, поэтому +srv к нему не применяется.
Параметры TLS: когда сбой не связан с аутентификацией#
Проблемы с TLS возникают до того, как сервер вообще увидит пароль, поэтому код 18 они не дают. Они дают ServerSelectionTimeoutError или ошибку рукопожатия с упоминанием сертификатов. Параметры всё же полезно знать, потому что изменение настроек TLS в погоне за ошибкой аутентификации нередко добавляет второй сбой поверх первого.
| Параметр | По умолчанию | Назначение |
|---|---|---|
tls | false (true с +srv) | Шифровать соединение |
tlsCAFile | системное хранилище | CA, подписавший частный сертификат |
tlsCertificateKeyFile | нет | Клиентский сертификат, для X.509 |
tlsAllowInvalidHostnames | false | Пропустить проверку имени - только для тестов |
tlsAllowInvalidCertificates | false | Пропустить все проверки - только для тестов |
ssl=true - более старый псевдоним для tls=true, он по-прежнему принимается. Если сервер использует сертификат от частного CA, укажите в tlsCAFile этот CA, а не прибегайте к tlsAllowInvalidCertificates, который принимает любой сертификат от кого угодно и лишает шифрование смысла. Если указать tls=true для сервера, не настроенного на TLS, рукопожатие завершится ошибкой независимо от учётных данных.
Один метод аутентификации зависит от TLS: клиентские сертификаты X.509. При authMechanism=MONGODB-X509 сертификат и есть учётные данные, пароля нет, а authSource должен быть $external - виртуальной базой, в которой хранятся пользователи, аутентифицируемые извне. LDAP (PLAIN), Kerberos (GSSAPI) и AWS IAM (MONGODB-AWS) тоже аутентифицируются через $external. Если вы скопировали URI с authSource=$external, а используете обычные имя и пароль, ошибка именно в этом.
Механизмы SCRAM
Обычные пользователи с паролем аутентифицируются через SCRAM. Начиная с MongoDB 4.0, новые пользователи по умолчанию получают учётные данные и для SCRAM-SHA-1, и для SCRAM-SHA-256, а драйверы договариваются о более стойком. Проблемы появляются, когда URI принудительно задаёт authMechanism=SCRAM-SHA-1, а пользователь создан только с SHA-256 (или наоборот), либо когда драйвер, слишком старый, чтобы знать SHA-256, встречает пользователя только с SHA-256. Решение почти всегда - удалить authMechanism из URI и дать драйверу договориться самому. Массив mechanisms пользователя покажет usersInfo, если нужно проверить.
Проверка подключения в mongosh#
mongosh - эталонный клиент. Если он подключается с каким-то набором значений, эти значения верны, а всё, что падает с теми же значениями, - проблема приложения или его окружения. Проверяйте в таком порядке, меняя что-то одно за раз.
- Подключитесь без учётных данных, чтобы проверить сетевой путь:
mongosh "mongodb://db.example.net:27017" --eval 'db.runCommand({ ping: 1 })'. Ответ{ ok: 1 }значит, что хост, порт и TLS в порядке; таймаут значит, что пора перестать смотреть на пароли. - Подключитесь с отдельными флагами и запрашиваемым паролем, чтобы не могли помешать ни кодирование, ни кавычки оболочки.
- Когда это заработает, подключитесь с полным URI в одинарных кавычках. Если шаг 2 успешен, а шаг 3 нет, проблема в URI - кодирование,
authSourceили параметр. - Наконец, запустите приложение с тем же URI. Если сбой здесь, приложение получает что-то другое.
$ mongosh --host db.example.net --port 27017 \ -u shopapp -p --authenticationDatabase admin shop-p без значения запрашивает пароль. --authenticationDatabase - это имя authSource в командной строке, что часто путает при переходе между двумя формами.
Учётные данные можно проверить и изнутри уже открытой сессии, что удобно, когда вы подключены как администратор и хотите попробовать другого пользователя:
db.getSiblingDB("admin").auth("shopapp", passwordPrompt())db.runCommand({ connectionStatus: 1 }).authInfoauth() возвращает { ok: 1 } при успехе, а иначе выбрасывает ошибку Authentication failed; база, на которой вы его вызываете, и есть проверяемый authSource. Затем connectionStatus показывает, какие пользователи и роли есть у сессии. Compass ведёт себя так же: в его расширенных параметрах на вкладке Authentication есть поле «Authentication Database», то есть authSource под другим названием.
Особенности отдельных драйверов#
Все драйверы принимают один и тот же URI, но у каждого свой способ передавать учётные данные отдельными параметрами и своя особенность, приводящая к коду 18.
Драйвер Node.js и Mongoose
Драйвер Node принимает учётные данные либо в URI, либо как auth: { username, password }, а authSource - как параметр верхнего уровня. Отдельные параметры не декодируются, поэтому передавайте туда исходный пароль.
const client = new MongoClient("mongodb://db.example.net:27017/shop", { auth: { username: "shopapp", password: process.env.MONGO_PASSWORD }, authSource: "admin",});Mongoose добавляет параметры dbName, user и pass. dbName меняет базу, которую используют ваши модели, но легко получить путь URI и dbName, которые расходятся в том, какая база имеется в виду. Когда вы используете dbName, задавайте authSource явно, а не полагайтесь на умолчание, и вопрос никогда не возникнет.
PyMongo
PyMongo принимает именованные аргументы username, password и authSource, которые полностью избавляют от кодирования. Если всё же собираете URI, функция urllib.parse.quote_plus - та, что использует документация PyMongo. PyMongo сообщает об ошибке как OperationFailure с code 18 и codeName: AuthenticationFailed в полном тексте ошибки; перехватывать вместо этого ServerSelectionTimeoutError и записывать «auth failed» - частый способ спрятать сетевую проблему за неверным сообщением.
client = MongoClient( "db.example.net", 27017, username="shopapp", password=os.environ["MONGO_PASSWORD"], authSource="admin",)Go, Java и PHP
Драйвер Go применяет URI, а затем позволяет переопределить учётные данные через options.Credential, поле AuthSource которого получает умолчание так же, как и в URI. Если вы вызываете SetAuth с учётными данными, где AuthSource пуст, посмотрите, что указано в пути URI. В Java MongoCredential.createScramSha256Credential(user, source, password) принимает базу аутентификации вторым аргументом - передайте туда базу приложения по привычке, и вы воспроизведёте классическую ошибку на новом языке. Библиотека PHP принимает параметры URI вторым аргументом new MongoDB\Client($uri, $uriOptions), где username, password и authSource можно задавать без кодирования.
Фреймворки, которые собирают URI за вас
Spring Boot, пакеты Laravel и подобные обёртки часто собирают URI из отдельных настроек - хоста, базы, имени пользователя и пароля. Выясните, как это делает ваш. Некоторые из них помещают настроенную базу в путь URI, из-за чего она становится authSource по умолчанию, и предоставляют отдельную настройку для базы аутентификации, которую легко не заметить. Если задействован фреймворк, один раз при запуске запишите в журнал итоговый URI (с маской); оттуда обычно всё становится понятно.
Сброс пароля и восстановление доступа#
Если пользователь существует, а пароль вы просто не знаете, администратор может задать новый, ничего больше не затрагивая:
db.getSiblingDB("admin").changeUserPassword("shopapp", passwordPrompt())Выполняйте команду на той базе, которой принадлежит пользователь: здесь это admin, а shop - если пользователь живёт там. Существующие подключения остаются аутентифицированными, поэтому после этого перезапустите приложение, чтобы убедиться, что новый пароль работает.
На сервере, который вы администрируете сами и где утеряны все пароли администраторов, задокументированный путь такой: перезапустить mongod с отключённым контролем доступа (удалить или закомментировать security.authorization: enabled в mongod.conf), привязав его только к 127.0.0.1, создать или сбросить администратора и снова включить авторизацию. Делайте это, пока база недоступна снаружи на всё время работ, потому что в эти минуты любой, кто может подключиться, фактически получает права root.
Когда пользователей нет совсем, MongoDB разрешает подключение с localhost для создания первого - это localhost exception. Как только появляется любой пользователь, исключение закрывается. Поэтому свежая самостоятельная установка иногда принимает неаутентифицированные команды с самого сервера и отвергает их отовсюду ещё.
На RE:NODE хостинге баз данных сервер MongoDB создаётся с учётными данными, сгенерированными именно для этого сервера, и доступен напрямую по хосту и порту, указанным в тарифе: перед ним нет прокси, так что описанные выше ловушки TLS и +srv не возникают, пока вы сами их не настроите. Если аутентификация не проходит, обычные подозреваемые - те, что описаны в этой статье: база, которой принадлежит пользователь, и то, как пароль попал в ваш URI. О том, как панель обращается с учётными данными баз, рассказывает раздел о базах данных в руководствах.
FAQ#
Почему MongoDB пишет authentication failed, если мой пароль верный?
Потому что сервер искал пользователя не в той базе, где тот был создан. Драйвер ищет в базе, указанной в authSource, а если параметра нет - в базе из пути URI. Пользователь, созданный в admin, и URI, оканчивающийся на /shop, дают ошибку каждый раз, пока вы не добавите ?authSource=admin.
Что значит 'bad auth : Authentication failed' в Atlas?
Это формулировка Atlas для той же самой ошибки. Проверьте, что вы используете пользователя базы со страницы Database Access, а не логин Atlas, что пароль в URI закодирован в процентной форме и что вы не переписали строку +srv в обычный список хостов, не вернув authSource=admin.
Как узнать, какой базе принадлежит пользователь MongoDB?
Подключитесь как администратор и выполните db.adminCommand({ usersInfo: { forAllDBs: true } }). В каждой записи есть поля user и db; значение db и нужно указывать в authSource. Без административного доступа строка журнала сервера о неудачной попытке покажет, какую базу запросил клиент.
Нужно ли кодировать пароль, если я передаю его отдельно?
Нет, и делать этого нельзя. Кодирование относится только к URI. Параметры драйвера вроде password и запрос -p в mongosh принимают исходный пароль; закодированное значение там отправляется с последовательностями % как есть и не проходит.
Совпадает ли authSource с базой, которую я хочу использовать?
Только если пользователь создан в этой базе. База в пути URI - это то, куда по умолчанию уходят ваши запросы; authSource - то, где сервер ищет вашу учётную запись. Они часто различаются, и именно поэтому этот параметр существует.
Можно ли использовать mongodb+srv с одним арендованным сервером?
Только если кто-то создал для него SRV-записи, а у одного арендованного сервера их обычно нет. Используйте mongodb:// с хостом и портом. Если нужно читаемое имя, достаточно A-записи, указывающей на адрес.


