diff --git a/doc/reference/libpurple/libpurple-docs.xml b/doc/reference/libpurple/libpurple-docs.xml
--- a/doc/reference/libpurple/libpurple-docs.xml
+++ b/doc/reference/libpurple/libpurple-docs.xml
@@ -78,6 +78,7 @@
       <xi:include href="xml/purpleimconversation.xml" />
       <xi:include href="xml/purplekeyvaluepair.xml" />
       <xi:include href="xml/purplemarkup.xml" />
+      <xi:include href="xml/purplenoopcredentialprovider.xml" />
       <xi:include href="xml/purpleoptions.xml" />
       <xi:include href="xml/purplepresence.xml" />
       <xi:include href="xml/purpleprotocolattention.xml" />
diff --git a/libpurple/core.c b/libpurple/core.c
--- a/libpurple/core.c
+++ b/libpurple/core.c
@@ -36,6 +36,7 @@
 #include "plugins.h"
 #include "prefs.h"
 #include "proxy.h"
+#include "purplecredentialmanager.h"
 #include "purplemessage.h"
 #include "purpleprivate.h"
 #include "savedstatuses.h"
@@ -208,12 +209,17 @@
 {
 	PurpleCoreUiOps *ops;
 	PurpleCore *core = purple_get_core();
+	PurpleCredentialManager *manager = NULL;
 
 	g_return_if_fail(core != NULL);
 
 	/* The self destruct sequence has been initiated */
 	purple_signal_emit(purple_get_core(), "quitting");
 
+	/* Remove the active provider in the credential manager. */
+	manager = purple_credential_manager_get_default();
+	purple_credential_manager_set_active_provider(manager, NULL, NULL);
+
 	/* Transmission ends */
 	purple_connections_disconnect_all();
 
@@ -231,7 +237,6 @@
 	purple_statuses_uninit();
 	purple_accounts_uninit();
 	purple_keyring_uninit(); /* after accounts */
-	purple_credential_manager_shutdown(); /* after accounts */
 	purple_theme_manager_uninit();
 	purple_xfers_uninit();
 	purple_proxy_uninit();
@@ -246,9 +251,11 @@
 	purple_prefs_uninit();
 	purple_plugins_uninit();
 
+	/* after plugins */
+	purple_credential_manager_shutdown();
 	purple_protocol_manager_shutdown();
+	purple_cmds_uninit();
 
-	purple_cmds_uninit();
 	purple_log_uninit();
 	/* Everything after util_uninit cannot try to write things to the
 	 * confdir.
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -56,6 +56,7 @@
 	'purplekeyvaluepair.c',
 	'purplemarkup.c',
 	'purplemessage.c',
+	'purplenoopcredentialprovider.c',
 	'purpleoptions.c',
 	'purplepresence.c',
 	'purpleprotocolattention.c',
@@ -147,6 +148,7 @@
 	'purplekeyvaluepair.h',
 	'purplemarkup.h',
 	'purplemessage.h',
+	'purplenoopcredentialprovider.h',
 	'purpleoptions.h',
 	'purplepresence.h',
 	'purpleprotocolattention.h',
diff --git a/libpurple/purplecredentialmanager.c b/libpurple/purplecredentialmanager.c
--- a/libpurple/purplecredentialmanager.c
+++ b/libpurple/purplecredentialmanager.c
@@ -23,6 +23,7 @@
 #include "core.h"
 #include "debug.h"
 #include "prefs.h"
+#include "purplenoopcredentialprovider.h"
 #include "purpleprivate.h"
 #include "util.h"
 
@@ -155,12 +156,17 @@
 static void
 purple_credential_manager_init(PurpleCredentialManager *manager) {
 	PurpleCredentialManagerPrivate *priv = NULL;
+	PurpleCredentialProvider *noop = NULL;
 
 	priv = purple_credential_manager_get_instance_private(manager);
 
 	priv->providers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
 	                                        g_object_unref);
 
+	noop = purple_noop_credential_provider_new();
+	purple_credential_manager_register_provider(manager, noop, NULL);
+	g_object_unref(G_OBJECT(noop));
+
 	/* Connect to the core-initialized signal so we can alert the user if we
 	 * were unable to find their credential provider.
 	 */
@@ -253,7 +259,21 @@
 
 void
 purple_credential_manager_shutdown(void) {
-	g_clear_object(&default_manager);
+	if(PURPLE_IS_CREDENTIAL_MANAGER(default_manager)) {
+		PurpleCredentialManagerPrivate *priv = NULL;
+		guint size = 0;
+
+		priv = purple_credential_manager_get_instance_private(default_manager);
+
+		size = g_hash_table_size(priv->providers);
+		if(size > 0) {
+			g_warning("purple_credential_manager_shutdown called while %d "
+				      "providers were still registered. Skipping shutdown",
+				      size);
+		} else {
+			g_clear_object(&default_manager);
+		}
+	}
 }
 
 /******************************************************************************
@@ -380,8 +400,7 @@
 
 	g_clear_object(&old);
 
-	purple_debug_info("PurpleCredentialProvider",
-	                  "set active provider to '%s'", id);
+	purple_debug_info("credential-manager", "set active provider to '%s'", id);
 
 	return TRUE;
 }
diff --git a/libpurple/purplenoopcredentialprovider.h b/libpurple/purplenoopcredentialprovider.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purplenoopcredentialprovider.h
@@ -0,0 +1,70 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <purple.h> may be included directly"
+#endif
+
+#ifndef PURPLE_NOOP_CREDENTIAL_PROVIDER_H
+#define PURPLE_NOOP_CREDENTIAL_PROVIDER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <purplecredentialprovider.h>
+
+/**
+ * SECTION:purplenoopcredentialprovider
+ * @section_id: libpurple-purplenoopcredentialprovider
+ * @title: No Operation Credential Provider Object
+ *
+ * This is the "None" credential provider that always returns failures so that
+ * the user is forced to enter a password.
+ */
+
+G_BEGIN_DECLS
+
+/**
+ * PurpleNoopCredentialProvider:
+ *
+ * #PurpleNoopCredentialProvider is a #PurpleCredentialProvider that does not
+ * actually provider credentials.  It is used to implement the default behavior
+ * of requiring users to input passwords.
+ *
+ * Since: 3.0.0
+ */
+
+#define PURPLE_TYPE_NOOP_CREDENTIAL_PROVIDER (purple_noop_credential_provider_get_type())
+G_DECLARE_FINAL_TYPE(PurpleNoopCredentialProvider,
+                     purple_noop_credential_provider,
+                     PURPLE, NOOP_CREDENTIAL_PROVIDER, PurpleCredentialProvider)
+
+/**
+ * purple_noop_credential_provider_new:
+ *
+ * Creates a new #PurpleNoopCredentialProvider instance.  You typically will
+ * not need to call this directly as #PurpleCredentialManager will create one
+ * for itself.
+ *
+ * Returns: (transfer full): The new #PurpleNoopCredentialProvider instance.
+ */
+PurpleCredentialProvider *purple_noop_credential_provider_new(void);
+
+G_END_DECLS
+
+#endif /* PURPLE_NOOP_CREDENTIAL_PROVIDER */
diff --git a/libpurple/purplenoopcredentialprovider.c b/libpurple/purplenoopcredentialprovider.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purplenoopcredentialprovider.c
@@ -0,0 +1,116 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n-lib.h>
+
+#include "purplenoopcredentialprovider.h"
+
+#include "purplecredentialmanager.h"
+
+struct _PurpleNoopCredentialProvider {
+	PurpleCredentialProvider parent;
+};
+
+G_DEFINE_TYPE(PurpleNoopCredentialProvider, purple_noop_credential_provider,
+              PURPLE_TYPE_CREDENTIAL_PROVIDER)
+
+/******************************************************************************
+ * PurpleCredentialProvider Implementation
+ *****************************************************************************/
+static void
+purple_noop_credential_provider_read_password_async(PurpleCredentialProvider *provider,
+                                                    PurpleAccount *account,
+                                                    GCancellable *cancellable,
+                                                    GAsyncReadyCallback callback,
+                                                    gpointer data)
+{
+	GTask *task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
+
+	g_task_return_new_error(task, PURPLE_CREDENTIAL_MANAGER_DOMAIN, 0,
+	                        _("provider does not store passwords"));
+
+	g_object_unref(G_OBJECT(task));
+}
+
+static gchar *
+purple_noop_credential_provider_read_password_finish(PurpleCredentialProvider *provider,
+                                                     GAsyncResult *result,
+                                                     GError **error)
+{
+	return g_task_propagate_pointer(G_TASK(result), error);
+}
+
+static void
+purple_noop_credential_provider_write_password_async(PurpleCredentialProvider *provider,
+                                                     PurpleAccount *account,
+                                                     const gchar *password,
+                                                     GCancellable *cancellable,
+                                                     GAsyncReadyCallback callback,
+                                                     gpointer data)
+{
+	GTask *task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
+
+	g_task_return_new_error(task, PURPLE_CREDENTIAL_MANAGER_DOMAIN, 0,
+	                        _("provider does not store passwords"));
+
+	g_object_unref(G_OBJECT(task));
+}
+
+static gboolean
+purple_noop_credential_provider_write_password_finish(PurpleCredentialProvider *provider,
+                                                      GAsyncResult *result,
+                                                      GError **error)
+{
+	return g_task_propagate_boolean(G_TASK(result), error);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_noop_credential_provider_init(PurpleNoopCredentialProvider *provider) {
+}
+
+static void
+purple_noop_credential_provider_class_init(PurpleNoopCredentialProviderClass *klass)
+{
+	PurpleCredentialProviderClass *provider_class = NULL;
+
+	provider_class = PURPLE_CREDENTIAL_PROVIDER_CLASS(klass);
+	provider_class->read_password_async =
+		purple_noop_credential_provider_read_password_async;
+	provider_class->read_password_finish =
+		purple_noop_credential_provider_read_password_finish;
+	provider_class->write_password_async =
+		purple_noop_credential_provider_write_password_async;
+	provider_class->write_password_finish =
+		purple_noop_credential_provider_write_password_finish;
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleCredentialProvider *
+purple_noop_credential_provider_new(void) {
+	return PURPLE_CREDENTIAL_PROVIDER(g_object_new(
+		PURPLE_TYPE_NOOP_CREDENTIAL_PROVIDER,
+		"id", "noop-provider",
+		"name", _("None"),
+		NULL
+	));
+}
diff --git a/pidgin/pidgincredentialproviderstore.c b/pidgin/pidgincredentialproviderstore.c
--- a/pidgin/pidgincredentialproviderstore.c
+++ b/pidgin/pidgincredentialproviderstore.c
@@ -156,12 +156,12 @@
 
 	manager = purple_credential_manager_get_default();
 
-	g_signal_connect(G_OBJECT(manager), "provider-registered",
-	                 G_CALLBACK(purple_credential_provider_store_registered_cb),
-	                 store);
-	g_signal_connect(G_OBJECT(manager), "provider-unregistered",
-	                 G_CALLBACK(purple_credential_provider_store_unregistered_cb),
-	                 store);
+	g_signal_connect_object(G_OBJECT(manager), "provider-registered",
+	                        G_CALLBACK(purple_credential_provider_store_registered_cb),
+	                        store, 0);
+	g_signal_connect_object(G_OBJECT(manager), "provider-unregistered",
+	                        G_CALLBACK(purple_credential_provider_store_unregistered_cb),
+	                        store, 0);
 }
 
 static void
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -253,6 +253,7 @@
 libpurple/purplekeyvaluepair.c
 libpurple/purplemarkup.c
 libpurple/purplemessage.c
+libpurple/purplenoopcredentialprovider.c
 libpurple/purpleoptions.c
 libpurple/purplepresence.c
 libpurple/purpleprotocolattention.c
