Index: trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 12820)
+++ trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 12821)
@@ -97,4 +97,5 @@
 import org.openstreetmap.josm.gui.bugreport.BugReportDialog;
 import org.openstreetmap.josm.gui.download.DownloadDialog;
+import org.openstreetmap.josm.gui.io.CredentialDialog;
 import org.openstreetmap.josm.gui.io.CustomConfigurator.XMLCommandProcessor;
 import org.openstreetmap.josm.gui.io.SaveLayersDialog;
@@ -132,4 +133,5 @@
 import org.openstreetmap.josm.io.OsmTransferCanceledException;
 import org.openstreetmap.josm.io.OsmTransferException;
+import org.openstreetmap.josm.io.auth.AbstractCredentialsAgent;
 import org.openstreetmap.josm.io.auth.CredentialsManager;
 import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
@@ -1089,4 +1091,5 @@
     static void setupCallbacks() {
         OsmConnection.setOAuthAccessTokenFetcher(OAuthAuthorizationWizard::obtainAccessToken);
+        AbstractCredentialsAgent.setCredentialsProvider(CredentialDialog::promptCredentials);
         MessageNotifier.setNotifierCallback(MainApplication::notifyNewMessages);
         DeleteCommand.setDeletionCallback(DeleteAction.defaultDeletionCallback);
Index: trunk/src/org/openstreetmap/josm/gui/io/CredentialDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/CredentialDialog.java	(revision 12820)
+++ trunk/src/org/openstreetmap/josm/gui/io/CredentialDialog.java	(revision 12821)
@@ -18,4 +18,5 @@
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
+import java.net.Authenticator.RequestorType;
 import java.util.Objects;
 
@@ -32,4 +33,5 @@
 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
 import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.WindowGeometry;
 import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
@@ -38,4 +40,6 @@
 import org.openstreetmap.josm.io.DefaultProxySelector;
 import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.auth.AbstractCredentialsAgent;
+import org.openstreetmap.josm.io.auth.CredentialsAgentResponse;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.InputMapUtils;
@@ -68,4 +72,34 @@
         dialog.pack();
         return dialog;
+    }
+
+    /**
+     * Prompts the user (in the EDT) for credentials and fills the given response with what has been entered.
+     * @param requestorType type of the entity requesting authentication
+     * @param agent the credentials agent requesting credentials
+     * @param response authentication response to fill
+     * @param username the known username, if any. Likely to be empty
+     * @param password the known password, if any. Likely to be empty
+     * @param host the host against authentication will be performed
+     * @since 12821
+     */
+    public static void promptCredentials(RequestorType requestorType, AbstractCredentialsAgent agent, CredentialsAgentResponse response,
+            String username, String password, String host) {
+        GuiHelper.runInEDTAndWait(() -> {
+            CredentialDialog dialog;
+            if (requestorType.equals(RequestorType.PROXY))
+                dialog = getHttpProxyCredentialDialog(
+                        username, password, host, agent.getSaveUsernameAndPasswordCheckboxText());
+            else
+                dialog = getOsmApiCredentialDialog(
+                        username, password, host, agent.getSaveUsernameAndPasswordCheckboxText());
+            dialog.setVisible(true);
+            response.setCanceled(dialog.isCanceled());
+            if (dialog.isCanceled())
+                return;
+            response.setUsername(dialog.getUsername());
+            response.setPassword(dialog.getPassword());
+            response.setSaveCredentials(dialog.isSaveCredentials());
+        });
     }
 
Index: trunk/src/org/openstreetmap/josm/io/auth/AbstractCredentialsAgent.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/auth/AbstractCredentialsAgent.java	(revision 12820)
+++ trunk/src/org/openstreetmap/josm/io/auth/AbstractCredentialsAgent.java	(revision 12821)
@@ -2,20 +2,48 @@
 package org.openstreetmap.josm.io.auth;
 
-import java.awt.GraphicsEnvironment;
 import java.net.Authenticator.RequestorType;
 import java.net.PasswordAuthentication;
 import java.util.EnumMap;
 import java.util.Map;
+import java.util.Objects;
 
-import org.openstreetmap.josm.gui.io.CredentialDialog;
-import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.tools.Logging;
 
 /**
  * Partial implementation of the {@link CredentialsAgent} interface.
  * <p>
- * Provides a memory cache for the credentials and means to query the information 
- * from the user.
+ * Provides a memory cache for the credentials and means to query the information from the user.
+ * @since 4246
  */
 public abstract class AbstractCredentialsAgent implements CredentialsAgent {
+
+    /**
+     * Synchronous credentials provider. Called if no credentials are cached. Can be used for user login prompt.
+     * @since 12821
+     */
+    @FunctionalInterface
+    public interface CredentialsProvider {
+        /**
+         * Fills the given response with appropriate user credentials.
+         * @param requestorType type of the entity requesting authentication
+         * @param agent the credentials agent requesting credentials
+         * @param response authentication response to fill
+         * @param username the known username, if any. Likely to be empty
+         * @param password the known password, if any. Likely to be empty
+         * @param host the host against authentication will be performed
+         */
+        void provideCredentials(RequestorType requestorType, AbstractCredentialsAgent agent, CredentialsAgentResponse response,
+                String username, String password, String host);
+    }
+
+    private static CredentialsProvider credentialsProvider = (a, b, c, d, e, f) -> Logging.error("Credentials provider has not been set");
+
+    /**
+     * Sets the global credentials provider.
+     * @param provider credentials provider. Called if no credentials are cached. Can be used for user login prompt
+     */
+    public static void setCredentialsProvider(CredentialsProvider provider) {
+        credentialsProvider = Objects.requireNonNull(provider, "provider");
+    }
 
     protected Map<RequestorType, PasswordAuthentication> memoryCredentialsCache = new EnumMap<>(RequestorType.class);
@@ -51,22 +79,5 @@
          */
         } else if (noSuccessWithLastResponse || username.isEmpty() || password.isEmpty()) {
-            if (!GraphicsEnvironment.isHeadless()) {
-                GuiHelper.runInEDTAndWait(() -> {
-                    CredentialDialog dialog;
-                    if (requestorType.equals(RequestorType.PROXY))
-                        dialog = CredentialDialog.getHttpProxyCredentialDialog(
-                                username, password, host, getSaveUsernameAndPasswordCheckboxText());
-                    else
-                        dialog = CredentialDialog.getOsmApiCredentialDialog(
-                                username, password, host, getSaveUsernameAndPasswordCheckboxText());
-                    dialog.setVisible(true);
-                    response.setCanceled(dialog.isCanceled());
-                    if (dialog.isCanceled())
-                        return;
-                    response.setUsername(dialog.getUsername());
-                    response.setPassword(dialog.getPassword());
-                    response.setSaveCredentials(dialog.isSaveCredentials());
-                });
-            }
+            credentialsProvider.provideCredentials(requestorType, this, response, username, password, host);
             if (response.isCanceled() || response.getUsername() == null || response.getPassword() == null) {
                 return response;
