Extension for replication authentication methods (#1081)

diff --git a/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js b/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
index 7d540ca..711a348 100644
--- a/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
+++ b/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
@@ -11,27 +11,27 @@
 // the License.
 
 
-var helpers = require('../../../../../test/nightwatch_tests/helpers/helpers.js');
-var testDbName = 'test_database';
+const helpers = require('../../../../../test/nightwatch_tests/helpers/helpers.js');
+const testDbName = 'test_database';
 module.exports = {
   before: function (client, done) {
-    var nano = helpers.getNanoInstance(client.globals.test_settings.db_url);
+    const nano = helpers.getNanoInstance(client.globals.test_settings.db_url);
     nano.db.create(testDbName, function () {
       done();
     });
   },
 
   after: function (client, done) {
-    var nano = helpers.getNanoInstance(client.globals.test_settings.db_url);
+    const nano = helpers.getNanoInstance(client.globals.test_settings.db_url);
     nano.db.destroy(testDbName, function () {
       done();
     });
   },
 
   'Shows correct view on replicate database': function (client) {
-    var waitTime = client.globals.maxWaitTime,
-        baseUrl = client.globals.test_settings.launch_url;
-    var srcDbSelector = '.replication__page .replication__section:nth-child(2) .replication__input-react-select .Select-value-label';
+    const waitTime = client.globals.maxWaitTime,
+          baseUrl = client.globals.test_settings.launch_url;
+    const srcDbSelector = '.replication__page .replication__section:nth-child(3) .replication__input-react-select .Select-value-label';
     client
       .loginToGUI()
       .url(baseUrl + '/#/database/' + testDbName + '/_all_docs')
@@ -40,13 +40,13 @@
       .clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
       .clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-replicate')
 
-    //Wait for replication page to show up
+      //Wait for replication page to show up
       .waitForElementVisible('.replication__page', waitTime, false)
 
-    //Wait for source select to show
+      //Wait for source select to show
       .waitForElementVisible(srcDbSelector, waitTime, false)
 
-    //Get the text values
+      //Get the text values
       .getText(srcDbSelector, function (data) {
         this.verify.ok(data.value === testDbName,
           'Check if database name is filled in source name');
diff --git a/app/addons/replication/__tests__/actions.test.js b/app/addons/replication/__tests__/actions.test.js
index 2e090e8..929a2c7 100644
--- a/app/addons/replication/__tests__/actions.test.js
+++ b/app/addons/replication/__tests__/actions.test.js
@@ -115,8 +115,12 @@
       "replicationType": "REPLICATION_TYPE_ONE_TIME",
       "replicationSource": "REPLICATION_SOURCE_LOCAL",
       "localSource": "animaldb",
+      "sourceAuthType":"BASIC_AUTH",
+      "sourceAuth":{"username":"tester", "password":"testerpass"},
       "replicationTarget": "REPLICATION_TARGET_EXISTING_LOCAL_DATABASE",
-      "localTarget": "boom123"
+      "localTarget": "boom123",
+      "targetAuthType":"BASIC_AUTH",
+      "targetAuth":{"username":"tester", "password":"testerpass"}
     };
 
     it('builds up correct state', (done) => {
@@ -130,6 +134,60 @@
       fetchMock.getOnce('/_replicator/7dcea9874a8fcb13c6630a1547001559', doc);
       getReplicationStateFrom(doc._id)(dispatch);
     });
+
+    it('builds up correct state with custom auth', (done) => {
+      const docWithCustomAuth = Object.assign(
+        {}, doc, {
+          "_id": "rep_custom_auth",
+          "continuous": true,
+          "source": {
+            "headers": {},
+            "url": "http://dev:8000/animaldb",
+            "auth": {
+              "creds": "source_user_creds"
+            }
+          },
+          "target": {
+            "headers": {},
+            "url": "http://dev:8000/boom123",
+            "auth": {
+              "creds": "target_user_creds"
+            }
+          }
+        });
+
+      const docStateWithCustomAuth = {
+        "replicationDocName": "rep_custom_auth",
+        "replicationType": "REPLICATION_TYPE_CONTINUOUS",
+        "replicationSource": "REPLICATION_SOURCE_LOCAL",
+        "localSource": "animaldb",
+        "sourceAuthType":"TEST_CUSTOM_AUTH",
+        "sourceAuth":{"creds":"source_user_creds"},
+        "replicationTarget": "REPLICATION_TARGET_EXISTING_LOCAL_DATABASE",
+        "localTarget": "boom123",
+        "targetAuthType":"TEST_CUSTOM_AUTH",
+        "targetAuth":{"creds":"target_user_creds"},
+      };
+      FauxtonAPI.registerExtension('Replication:Auth', {
+        typeValue: 'TEST_CUSTOM_AUTH',
+        typeLabel: 'Test Custom Auth',
+        getCredentials: (repSourceOrTarget) => {
+          if (repSourceOrTarget.auth && repSourceOrTarget.auth.creds) {
+            return { creds: repSourceOrTarget.auth.creds };
+          }
+          return undefined;
+        }
+      });
+      const dispatch = ({type, options}) => {
+        if (ActionTypes.REPLICATION_SET_STATE_FROM_DOC === type) {
+          assert.deepEqual(docStateWithCustomAuth, options);
+          setTimeout(done);
+        }
+      };
+
+      fetchMock.getOnce('/_replicator/rep_custom_auth', docWithCustomAuth);
+      getReplicationStateFrom(docWithCustomAuth._id)(dispatch);
+    });
   });
 
   describe('deleteDocs', () => {
diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js
index e7f31b0..113c75b 100644
--- a/app/addons/replication/__tests__/api.tests.js
+++ b/app/addons/replication/__tests__/api.tests.js
@@ -9,6 +9,7 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
+import FauxtonAPI from '../../../core/api';
 import utils from '../../../../test/mocha/testUtils';
 import {
   getSource,
@@ -66,12 +67,14 @@
 
     it('returns local source with auth info and encoded', () => {
       const localSource = 'my/db';
-
       const source = getSource({
         replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
         localSource,
-        username: 'the-user',
-        password: 'password'
+        sourceAuth: {
+          username: 'the-user',
+          password: 'password'
+        },
+        sourceAuthType: Constants.REPLICATION_AUTH_METHOD.BASIC
       }, {origin: 'http://dev:6767'});
 
       assert.deepEqual(source.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
@@ -81,15 +84,62 @@
     it('returns remote source url and auth header', () => {
       const source = getSource({
         replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
-        remoteSource: 'http://eddie:my-password@my-couchdb.com/my-db',
+        remoteSource: 'http://my-couchdb.com/my-db',
         localSource: "local",
-        username: 'the-user',
-        password: 'password'
+        sourceAuth: {
+          username: 'the-user',
+          password: 'password'
+        },
+        sourceAuthType: Constants.REPLICATION_AUTH_METHOD.BASIC
       }, {origin: 'http://dev:6767'});
 
-      assert.deepEqual(source.headers, {Authorization:"Basic ZWRkaWU6bXktcGFzc3dvcmQ="});
+      assert.deepEqual(source.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
       assert.deepEqual('http://my-couchdb.com/my-db', source.url);
     });
+
+    it('returns source with no auth', () => {
+      const source = getSource({
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        remoteSource: 'http://my-couchdb.com/my-db',
+        localSource: "local",
+        sourceAuth: {
+          username: 'the-user',
+          password: 'password'
+        },
+        sourceAuthType: Constants.REPLICATION_AUTH_METHOD.NO_AUTH
+      }, {origin: 'http://dev:6767'});
+
+      assert.deepEqual(source.headers, {});
+
+      const source2 = getSource({
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        remoteSource: 'http://my-couchdb.com/my-db',
+        localSource: "local"
+      }, {origin: 'http://dev:6767'});
+
+      assert.deepEqual(source2.headers, {});
+    });
+
+    it('returns source with custom auth', () => {
+      FauxtonAPI.registerExtension('Replication:Auth', {
+        typeValue: 'TEST_CUSTOM_AUTH',
+        typeLabel: 'Test Custom Auth',
+        setCredentials: (repSourceOrTarget, auth) => {
+          repSourceOrTarget.auth = {
+            auth_creds: auth.creds
+          };
+        }
+      });
+      const source = getSource({
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        remoteSource: 'http://my-couchdb.com/my-db',
+        localSource: "local",
+        sourceAuth: { creds: 'sample_creds' },
+        sourceAuthType: 'TEST_CUSTOM_AUTH'
+      }, {origin: 'http://dev:6767'});
+
+      assert.deepEqual(source.auth, { auth_creds: 'sample_creds' });
+    });
   });
 
   describe('getTarget', () => {
@@ -104,12 +154,15 @@
     });
 
     it("encodes username and password for remote", () => {
-      const remoteTarget = 'http://jimi:my-password@remote-couchdb.com/my/db';
+      const remoteTarget = 'http://remote-couchdb.com/my/db';
       const target = getTarget({
         replicationTarget: Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE,
         remoteTarget: remoteTarget,
-        username: 'fake',
-        password: 'fake'
+        targetAuth: {
+          username: 'jimi',
+          password: 'my-password'
+        },
+        targetAuthType: Constants.REPLICATION_AUTH_METHOD.BASIC
       });
 
       assert.deepEqual(target.url, 'http://remote-couchdb.com/my%2Fdb');
@@ -120,8 +173,11 @@
       const target = getTarget({
         replicationTarget: Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE,
         localTarget: 'my-existing/db',
-        username: 'the-user',
-        password: 'password'
+        targetAuth: {
+          username: 'the-user',
+          password: 'password'
+        },
+        targetAuthType: Constants.REPLICATION_AUTH_METHOD.BASIC
       });
 
       assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
@@ -133,8 +189,11 @@
         replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
         replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
         localTarget: 'my-new/db',
-        username: 'the-user',
-        password: 'password'
+        targetAuth: {
+          username: 'the-user',
+          password: 'password'
+        },
+        targetAuthType: Constants.REPLICATION_AUTH_METHOD.BASIC
       });
 
       assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
@@ -146,8 +205,11 @@
         replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
         replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
         localTarget: 'my-new/db',
-        username: 'the-user',
-        password: 'password'
+        targetAuth: {
+          username: 'the-user',
+          password: 'password'
+        },
+        targetAuthType: Constants.REPLICATION_AUTH_METHOD.BASIC
       }, {origin: 'http://dev:5555'});
 
       assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
@@ -173,6 +235,35 @@
 
       assert.deepEqual("http://dev:8000/my-new%2Fdb", target.url);
       assert.deepEqual({}, target.headers);
+
+      const targetNoAuth = getTarget({
+        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        localTarget: 'my-new/db',
+        targetAuthType: Constants.REPLICATION_AUTH_METHOD.NO_AUTH
+      }, location);
+      assert.deepEqual({}, targetNoAuth.headers);
+    });
+
+    it('returns target with custom auth', () => {
+      FauxtonAPI.registerExtension('Replication:Auth', {
+        typeValue: 'TEST_CUSTOM_AUTH',
+        typeLabel: 'Test Custom Auth',
+        setCredentials: (repSourceOrTarget, auth) => {
+          repSourceOrTarget.auth = {
+            auth_creds: auth.creds
+          };
+        }
+      });
+      const target = getTarget({
+        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
+        replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
+        localTarget: 'my-new/db',
+        targetAuth: { creds: 'sample_creds' },
+        targetAuthType: 'TEST_CUSTOM_AUTH'
+      });
+
+      assert.deepEqual(target.auth, { auth_creds: 'sample_creds' });
     });
 
   });
@@ -331,6 +422,30 @@
 
   });
 
+  describe('setCredentials', () => {
+
+    it('returns true for support', () => {
+      fetchMock.getOnce('/_scheduler/jobs', {});
+      return supportNewApi(true)
+        .then(resp => {
+          assert.ok(resp);
+        });
+    });
+
+    it('returns false for no support', () => {
+      fetchMock.getOnce('/_scheduler/jobs', {
+        status: 404,
+        body: {error: "missing"}
+      });
+
+      return supportNewApi(true)
+        .then(resp => {
+          assert.notOk(resp);
+        });
+    });
+
+  });
+
   describe("fetchReplicationDocs", () => {
     const _repDocs = {
       "total_rows":2,
diff --git a/app/addons/replication/__tests__/newreplication.test.js b/app/addons/replication/__tests__/newreplication.test.js
index 7103a27..a5a9a2f 100644
--- a/app/addons/replication/__tests__/newreplication.test.js
+++ b/app/addons/replication/__tests__/newreplication.test.js
@@ -44,14 +44,34 @@
         updateFormField={() => { return () => {}; }}
       />);
 
-      assert.ok(newreplication.instance().validate());
+      assert.ok(newreplication.instance().checkSourceTargetDatabases());
     });
 
     it('returns true for remote source and target selected', () => {
       const newreplication = shallow(<NewReplication
         databases={[]}
         replicationTarget={Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE}
-        remoteTarget={"mydb"}
+        remoteTarget={"https://mydb.com/db2"}
+        remoteSource={"https://mydb.com/db1"}
+        localTarget={""}
+        localSource={""}
+        replicationSource={""}
+        replicationType={""}
+        replicationDocName={""}
+        conflictModalVisible={false}
+        clearReplicationForm={() => {}}
+        hideConflictModal={() => {}}
+        updateFormField={() => { return () => {}; }}
+      />);
+
+      assert.ok(newreplication.instance().checkSourceTargetDatabases());
+    });
+
+    it('returns false for invalid remote source', () => {
+      const newreplication = shallow(<NewReplication
+        databases={[]}
+        replicationTarget={Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE}
+        remoteTarget={"https://mydb.com/db"}
         remoteSource={"anotherdb"}
         localTarget={""}
         localSource={""}
@@ -64,7 +84,27 @@
         updateFormField={() => { return () => {}; }}
       />);
 
-      assert.ok(newreplication.instance().validate());
+      assert.notOk(newreplication.instance().checkSourceTargetDatabases());
+    });
+
+    it('returns false for invalid remote target', () => {
+      const newreplication = shallow(<NewReplication
+        databases={[]}
+        replicationTarget={Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE}
+        remoteTarget={"anotherdb"}
+        remoteSource={"https://mydb.com/db"}
+        localTarget={""}
+        localSource={""}
+        replicationSource={""}
+        replicationType={""}
+        replicationDocName={""}
+        conflictModalVisible={false}
+        clearReplicationForm={() => {}}
+        hideConflictModal={() => {}}
+        updateFormField={() => { return () => {}; }}
+      />);
+
+      assert.notOk(newreplication.instance().checkSourceTargetDatabases());
     });
 
     it("warns if new local database exists", () => {
@@ -86,7 +126,7 @@
         updateFormField={() => { return () => {}; }}
       />);
 
-      newreplication.instance().validate();
+      newreplication.instance().checkSourceTargetDatabases();
       assert.ok(spy.calledOnce);
 
       const notification = spy.args[0][0];
@@ -112,7 +152,7 @@
         updateFormField={() => { return () => {}; }}
       />);
 
-      newreplication.instance().validate();
+      newreplication.instance().checkSourceTargetDatabases();
       assert.ok(spy.calledOnce);
 
       const notification = spy.args[0][0];
@@ -138,7 +178,7 @@
         updateFormField={() => { return () => {}; }}
       />);
 
-      newreplication.instance().validate();
+      newreplication.instance().checkSourceTargetDatabases();
       assert.ok(spy.calledOnce);
 
       const notification = spy.args[0][0];
@@ -151,8 +191,8 @@
       const newreplication = shallow(<NewReplication
         databases={[]}
         replicationTarget={Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE}
-        remoteTarget={"samedb"}
-        remoteSource={"samedb"}
+        remoteTarget={"http://localhost/samedb"}
+        remoteSource={"http://localhost/samedb"}
         localTarget={""}
         localSource={""}
         replicationSource={""}
@@ -164,7 +204,7 @@
         updateFormField={() => { return () => {}; }}
       />);
 
-      newreplication.instance().validate();
+      newreplication.instance().checkSourceTargetDatabases();
       assert.ok(spy.calledOnce);
 
       const notification = spy.args[0][0];
@@ -301,9 +341,9 @@
       newreplication.instance().runReplicationChecks();
     });
 
-    it("Shows password modal", () => {
+    it("calls auth checks", () => {
       let called = false;
-      const showPasswordModal = () => {called = true;};
+      const checkAuth = () => {called = true;};
       const checkReplicationDocID = () => {
         const promise = FauxtonAPI.Deferred();
         promise.resolve(false);
@@ -326,11 +366,51 @@
         updateFormField={() => { return () => {}; }}
       />);
 
-      newreplication.instance().showPasswordModal = showPasswordModal;
+      newreplication.instance().checkAuth = checkAuth;
       newreplication.instance().runReplicationChecks();
       assert.ok(called);
     });
 
   });
 
+  describe("checkAuth", () => {
+
+    afterEach(() => {
+      restore(FauxtonAPI.addNotification);
+      FauxtonAPI.session = undefined;
+    });
+
+    it("prompts user for local target auth method", () => {
+      const spy = sinon.spy(FauxtonAPI, 'addNotification');
+      FauxtonAPI.session = {
+        isAdminParty: () => false
+      };
+      const newreplication = shallow(<NewReplication
+        replicationDocName="my-doc-id"
+        checkReplicationDocID={() => {}}
+        showConflictModal={() => {}}
+        databases={[]}
+        replicationSource={Constants.REPLICATION_SOURCE.REMOTE}
+        replicationTarget={Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE}
+        targetAuthType={Constants.REPLICATION_AUTH_METHOD.NO_AUTH}
+        remoteTarget={""}
+        remoteSource={""}
+        localTarget={""}
+        localSource={""}
+        replicationType={""}
+        conflictModalVisible={false}
+        clearReplicationForm={() => {}}
+        hideConflictModal={() => {}}
+        updateFormField={() => { return () => {}; }}
+      />);
+
+      newreplication.instance().checkAuth();
+      sinon.assert.calledWith(spy, {
+        msg: 'Missing credentials for local target database.',
+        clear: true,
+        type: 'error'
+      });
+    });
+  });
+
 });
diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js
index ba8199c..08e0733 100644
--- a/app/addons/replication/actions.js
+++ b/app/addons/replication/actions.js
@@ -9,6 +9,7 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
+import base64 from 'base-64';
 import FauxtonAPI from '../../core/api';
 import {get, post} from '../../core/ajax';
 import ActionTypes from './actiontypes';
@@ -87,10 +88,8 @@
       if (json.error && json.error === "not_found") {
         return createReplicatorDB().then(() => {
           return replicate(params);
-        })
-          .catch(handleError);
+        }).catch(handleError);
       }
-
       handleError(json);
     });
 };
@@ -283,6 +282,45 @@
     });
 };
 
+const getAuthTypeAndCredentials = (repSourceOrTarget) => {
+  const authTypeAndCreds = {
+    type: Constants.REPLICATION_AUTH_METHOD.NO_AUTH,
+    creds: {}
+  };
+  if (repSourceOrTarget.headers && repSourceOrTarget.headers.Authorization) {
+    // Removes 'Basic ' prefix
+    const encodedCreds = repSourceOrTarget.headers.Authorization.substring(6);
+    const decodedCreds = base64.decode(encodedCreds);
+    authTypeAndCreds.type = Constants.REPLICATION_AUTH_METHOD.BASIC;
+    authTypeAndCreds.creds = {
+      username: decodedCreds.split(':')[0],
+      password: decodedCreds.split(':')[1]
+    };
+    return authTypeAndCreds;
+  }
+
+  // Tries to get creds using one of the custom auth methods
+  // The extension should provide:
+  //   - 'getCredentials(obj)' method that extracts the credentials from obj which is the 'target'/'source' field of the replication doc.
+  //   - 'typeValue' field with an arbitrary ID representing the auth type the extension supports.
+  const authExtensions = FauxtonAPI.getExtensions('Replication:Auth');
+  let credentials = undefined;
+  let customAuthType = undefined;
+  if (authExtensions) {
+    authExtensions.map(ext => {
+      if (!credentials && ext.getCredentials) {
+        credentials = ext.getCredentials(repSourceOrTarget);
+        customAuthType = ext.typeValue;
+      }
+    });
+  }
+  if (credentials) {
+    authTypeAndCreds.type = customAuthType;
+    authTypeAndCreds.creds = credentials;
+  }
+  return authTypeAndCreds;
+};
+
 export const getReplicationStateFrom = (id) => dispatch => {
   dispatch({
     type: ActionTypes.REPLICATION_FETCHING_FORM_STATE
@@ -306,6 +344,9 @@
         stateDoc.replicationSource = Constants.REPLICATION_SOURCE.REMOTE;
         stateDoc.remoteSource = decodeFullUrl(sourceUrl);
       }
+      const sourceAuth = getAuthTypeAndCredentials(doc.source);
+      stateDoc.sourceAuthType = sourceAuth.type;
+      stateDoc.sourceAuth = sourceAuth.creds;
 
       if (targetUrl.indexOf(window.location.hostname) > -1) {
         const url = new URL(targetUrl);
@@ -315,6 +356,9 @@
         stateDoc.replicationTarget = Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE;
         stateDoc.remoteTarget = decodeFullUrl(targetUrl);
       }
+      const targetAuth = getAuthTypeAndCredentials(doc.target);
+      stateDoc.targetAuthType = targetAuth.type;
+      stateDoc.targetAuth = targetAuth.creds;
 
       dispatch({
         type: ActionTypes.REPLICATION_SET_STATE_FROM_DOC,
@@ -358,15 +402,3 @@
     });
   });
 };
-
-export const showPasswordModal = () => {
-  return {
-    type: ActionTypes.REPLICATION_SHOW_PASSWORD_MODAL
-  };
-};
-
-export const hidePasswordModal = () => {
-  return {
-    type: ActionTypes.REPLICATION_HIDE_PASSWORD_MODAL
-  };
-};
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index 2e24064..363228d 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -95,53 +95,63 @@
   replicationSource,
   localSource,
   remoteSource,
-  username,
-  password
+  sourceAuthType,
+  sourceAuth
 },
 {origin} = window.location) => {
-  let url;
-  let headers;
+
+  const source = {};
   if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) {
-    url = `${origin}/${localSource}`;
-    headers = getAuthHeaders(username, password);
+    source.url = encodeFullUrl(`${origin}/${localSource}`);
   } else {
-    const credentials = getCredentialsFromUrl(remoteSource);
-    headers = getAuthHeaders(credentials.username, credentials.password);
-    url = removeCredentialsFromUrl(remoteSource);
+    source.url = encodeFullUrl(removeCredentialsFromUrl(remoteSource));
   }
 
-  return {
-    headers,
-    url: encodeFullUrl(url)
-  };
+  setCredentials(source, sourceAuthType, sourceAuth);
+  return source;
 };
 
 export const getTarget = ({
   replicationTarget,
   localTarget,
   remoteTarget,
-  username,
-  password
+  targetAuthType,
+  targetAuth
 },
-{origin} = window.location //this allows us to mock out window.location for our tests
-) => {
+//this allows us to mock out window.location for our tests
+{origin} = window.location) => {
 
-  const encodedLocalTarget = encodeURIComponent(localTarget);
-  let headers = getAuthHeaders(username, password);
-  let target = `${origin}/${encodedLocalTarget}`;
-
+  const target = {};
   if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
         replicationTarget === Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE) {
-
-    const credentials = getCredentialsFromUrl(remoteTarget);
-    target = encodeFullUrl(removeCredentialsFromUrl(remoteTarget));
-    headers = getAuthHeaders(credentials.username, credentials.password);
+    target.url = encodeFullUrl(removeCredentialsFromUrl(remoteTarget));
+  } else {
+    const encodedLocalTarget = encodeURIComponent(localTarget);
+    target.url = `${origin}/${encodedLocalTarget}`;
   }
 
-  return {
-    headers: headers,
-    url: target
-  };
+  setCredentials(target, targetAuthType, targetAuth);
+  return target;
+};
+
+const setCredentials = (target, authType, auth) => {
+  if (!authType || authType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) {
+    target.headers = {};
+  } else if (authType === Constants.REPLICATION_AUTH_METHOD.BASIC) {
+    target.headers = getAuthHeaders(auth.username, auth.password);
+  } else {
+    // Tries to set creds using one of the custom auth methods
+    // The extension should provide:
+    //   - 'setCredentials(target, auth)' method which sets the 'auth' credentials into 'target' which is the 'target'/'source' field of the replication doc.
+    const authExtensions = FauxtonAPI.getExtensions('Replication:Auth');
+    if (authExtensions) {
+      authExtensions.filter(ext => ext.typeValue === authType).map(ext => {
+        if (ext.setCredentials) {
+          ext.setCredentials(target, auth);
+        }
+      });
+    }
+  }
 };
 
 export const createTarget = (replicationTarget) => {
@@ -180,12 +190,15 @@
   replicationSource,
   replicationType,
   replicationDocName,
-  password,
   localTarget,
   localSource,
   remoteTarget,
   remoteSource,
-  _rev
+  _rev,
+  sourceAuthType,
+  sourceAuth,
+  targetAuthType,
+  targetAuth
 }) => {
   const username = getUsername();
   return addDocIdAndRev(replicationDocName, _rev, {
@@ -197,16 +210,16 @@
       replicationSource,
       localSource,
       remoteSource,
-      username,
-      password
+      sourceAuthType,
+      sourceAuth
     }),
     target: getTarget({
       replicationTarget,
       replicationSource,
       remoteTarget,
       localTarget,
-      username,
-      password
+      targetAuthType,
+      targetAuth
     }),
     create_target: createTarget(replicationTarget),
     continuous: continuous(replicationType),
diff --git a/app/addons/replication/assets/less/replication.less b/app/addons/replication/assets/less/replication.less
index c1a56de..6a8a1aa 100644
--- a/app/addons/replication/assets/less/replication.less
+++ b/app/addons/replication/assets/less/replication.less
@@ -13,6 +13,8 @@
 @import "../../../../../assets/less/variables.less";
 @import "../../../../../assets/less/mixins.less";
 
+@replication_input_field_width: 400px;
+
 div.replication__page {
   padding-top: 25px !important;
   display: flex;
@@ -24,6 +26,10 @@
   display: flex;
   flex-flow: row wrap;
   justify-content: flex-start;
+  & input {
+    width: @replication_input_field_width;
+    font-size: 14px;
+  }
 }
 
 .replication__seperator {
@@ -44,27 +50,28 @@
   width: 540px;
   select {
     font-size: 14px;
-    width: 400px;
+    width: @replication_input_field_width;
     margin-bottom: 10px;
     background-color: white;
     border: 1px solid #cccccc;
   }
   .styled-select {
-    width: 400px;
+    width: @replication_input_field_width;
   }
 }
 
 .replication__input-react-select {
   font-size: 14px;
+  padding-bottom: 10px;
 
   .Select .Select-menu-outer {
-    width: 400px;
+    width: @replication_input_field_width;
   }
 
   .Select div.Select-control {
     padding: 6px;
     border: 1px solid #cccccc;
-    width: 400px;
+    width: @replication_input_field_width;
 
     .Select-value, .Select-placeholder {
       padding: 6px 15px 6px 10px;
@@ -84,7 +91,7 @@
 
 .replication__remote-connection-url[type="text"] {
   font-size: 14px;
-  width: 400px;
+  width: @replication_input_field_width;
   color: #333;
 }
 
@@ -95,14 +102,14 @@
 }
 
 .replication__new-input[type="text"] {
-  width: 400px;
+  width: @replication_input_field_width;
   font-size: 14px;
   color: #333;
 }
 
 .replication__doc-name {
   position: relative;
-  width: 400px;
+  width: @replication_input_field_width;
 
 }
 
@@ -125,7 +132,7 @@
 .replication__doc-name-input[type="text"] {
   padding-right: 32px;
   font-size: 14px;
-  width: 400px;
+  width: @replication_input_field_width;
   color: #333;
 }
 
@@ -342,4 +349,4 @@
 
 .replication__activity-caveat {
   padding-left: 80px;
-}
+}
\ No newline at end of file
diff --git a/app/addons/replication/components/auth-options.js b/app/addons/replication/components/auth-options.js
new file mode 100644
index 0000000..102a73e
--- /dev/null
+++ b/app/addons/replication/components/auth-options.js
@@ -0,0 +1,176 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import PropTypes from 'prop-types';
+import FauxtonAPI from '../../../core/api';
+import app from '../../../app';
+import React from 'react';
+import Constants from '../constants';
+import Components from '../../components/react-components';
+
+const { StyledSelect } = Components;
+
+export class ReplicationAuth extends React.Component {
+
+  constructor (props) {
+    super(props);
+    this.onChangeType = this.onChangeType.bind(this);
+    this.onChangeValue = this.onChangeValue.bind(this);
+
+    // init auth extensions
+    // The extension should provide:
+    //   - 'inputComponent' a React component that will be displayed when the user selects the auth method.
+    //   - 'typeValue' field with an arbitrary ID representing the auth type the extension supports.
+    //   - 'typeLabel' field containing the display label for the authentication method.
+    this.customAuths = FauxtonAPI.getExtensions('Replication:Auth');
+    if (!this.customAuths) {
+      this.customAuths = [];
+    }
+    this.customAuthTypes = this.customAuths.map(auth => auth.typeValue);
+  }
+
+  getAuthOptions = () => {
+    const userPasswordLabel = app.i18n.en_US['replication-user-password-auth-label'];
+    const authOptions = [
+      { value: Constants.REPLICATION_AUTH_METHOD.NO_AUTH, label: 'None' },
+      { value: Constants.REPLICATION_AUTH_METHOD.BASIC, label: userPasswordLabel }
+    ];
+    this.customAuths.map(auth => {
+      authOptions.push({ value: auth.typeValue, label: auth.typeLabel });
+    });
+
+    return authOptions.map(option => <option value={option.value} key={option.value}>{option.label}</option>);
+  }
+
+  onChangeType(newType) {
+    this.props.onChangeAuthType(newType);
+  }
+
+  onChangeValue(newValue) {
+    this.props.onChangeAuth(newValue);
+  }
+
+  getAuthInputFields(authValue, authType) {
+    const {authId} = this.props;
+    if (authType == Constants.REPLICATION_AUTH_METHOD.BASIC) {
+      return <UserPasswordAuthInput onChange={this.onChangeValue} auth={authValue} authId={authId}/>;
+    }
+    const matchedAuths = this.customAuths.filter(el => el.typeValue === authType);
+    if (matchedAuths && matchedAuths.length > 0) {
+      const InputComp = matchedAuths[0].inputComponent;
+      return <InputComp onChange={this.onChangeValue} auth={authValue} />;
+    }
+
+    return null;
+  }
+
+  render () {
+    const {credentials, authType, authId} = this.props;
+    return (<React.Fragment>
+      <div className="replication__section">
+        <div className="replication__input-label">
+          Authentication:
+        </div>
+        <div className="replication__input-select">
+          <StyledSelect
+            selectContent={this.getAuthOptions()}
+            selectChange={(e) => this.onChangeType(e.target.value)}
+            selectId={'select-' + authId}
+            selectValue={authType} />
+        </div>
+      </div>
+      {this.getAuthInputFields(credentials, authType)}
+    </React.Fragment>);
+  }
+}
+
+ReplicationAuth.propTypes = {
+  authId: PropTypes.string.isRequired,
+  authType: PropTypes.string.isRequired,
+  credentials: PropTypes.object,
+  onChangeAuth: PropTypes.func.isRequired,
+  onChangeAuthType: PropTypes.func.isRequired
+};
+
+ReplicationAuth.defaultProps = {
+  authType: Constants.REPLICATION_AUTH_METHOD.NO_AUTH,
+  onChangeAuthType: () => {},
+  onChangeAuth: () => {}
+};
+
+export class UserPasswordAuthInput extends React.Component {
+
+  constructor (props) {
+    super(props);
+    this.updatePassword = this.updatePassword.bind(this);
+    this.updateUsername = this.updateUsername.bind(this);
+    this.state = {
+      username: props.auth && props.auth.username ? props.auth.username : '',
+      password: props.auth && props.auth.password ? props.auth.password : ''
+    };
+  }
+
+  updatePassword(newValue) {
+    this.setState({password: newValue});
+    this.props.onChange({
+      username: this.state.username,
+      password: newValue
+    });
+  }
+
+  updateUsername(newValue) {
+    this.setState({username: newValue});
+    this.props.onChange({
+      username: newValue,
+      password: this.state.password
+    });
+  }
+
+  render () {
+    const usernamePlaceholder = app.i18n.en_US['replication-username-input-placeholder'];
+    const passwordPlaceholder = app.i18n.en_US['replication-password-input-placeholder'];
+    const { authId } = this.props;
+    return (
+      <React.Fragment>
+        <div className="replication__section">
+          <div className="replication__input-label"></div>
+          <div>
+            <input
+              id={authId + '-username'}
+              type="text"
+              placeholder={usernamePlaceholder}
+              value={this.state.username}
+              onChange={(e) => this.updateUsername(e.target.value)}
+              readOnly={this.props.usernameReadOnly}
+            />
+          </div>
+        </div>
+        <div className="replication__section">
+          <div className="replication__input-label"></div>
+          <div>
+            <input
+              id={authId + '-password'}
+              type="password"
+              placeholder={passwordPlaceholder}
+              value={this.state.password}
+              onChange={(e) => this.updatePassword(e.target.value)}
+            />
+          </div>
+        </div>
+      </React.Fragment>
+    );
+  }
+}
+
+UserPasswordAuthInput.propTypes = {
+  auth: PropTypes.object.isRequired,
+  onChange: PropTypes.func.isRequired
+};
diff --git a/app/addons/replication/components/newreplication.js b/app/addons/replication/components/newreplication.js
index f1a82a0..87b37e2 100644
--- a/app/addons/replication/components/newreplication.js
+++ b/app/addons/replication/components/newreplication.js
@@ -11,49 +11,94 @@
 // the License.
 
 import React from 'react';
-import app from '../../../app';
 import FauxtonAPI from '../../../core/api';
 import {ReplicationSource} from './source';
 import {ReplicationTarget} from './target';
 import {ReplicationOptions} from './options';
 import {ReplicationSubmit} from './submit';
-import AuthComponents from '../../auth/components';
+import {ReplicationAuth} from './auth-options';
+import AuthAPI from '../../auth/api';
 import Constants from '../constants';
 import {ConflictModal} from './modals';
 import {isEmpty} from 'lodash';
 
-const {PasswordModal} = AuthComponents;
-
 export default class NewReplicationController extends React.Component {
   constructor (props) {
     super(props);
     this.submit = this.submit.bind(this);
-    this.clear = this.clear.bind(this);
-    this.showPasswordModal = this.showPasswordModal.bind(this);
+    this.checkAuth = this.checkAuth.bind(this);
     this.runReplicationChecks = this.runReplicationChecks.bind(this);
   }
 
-  clear (e) {
-    e.preventDefault();
-    this.props.clearReplicationForm();
-  }
-
-  showPasswordModal () {
+  checkAuth () {
     this.props.hideConflictModal();
-    const { replicationSource, replicationTarget } = this.props;
+    const { replicationSource, replicationTarget,
+      sourceAuthType, targetAuthType, sourceAuth, targetAuth } = this.props;
 
-    const hasLocalSourceOrTarget = (replicationSource === Constants.REPLICATION_SOURCE.LOCAL ||
-      replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE ||
-      replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE);
+    const isLocalSource = replicationSource === Constants.REPLICATION_SOURCE.LOCAL;
+    const isLocalTarget = replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE ||
+      replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE;
 
-    // if the user is authenticated, or if NEITHER the source nor target are local, just submit. The password
-    // modal isn't necessary or if couchdb is in admin party mode
-    if (!hasLocalSourceOrTarget || this.props.authenticated || FauxtonAPI.session.isAdminParty()) {
-      this.submit(this.props.username, this.props.password);
-      return;
+    // Ask user to select an auth method for local source/target when one is not selected
+    // and not on admin party
+    if (!FauxtonAPI.session.isAdminParty()) {
+      if (isLocalSource && sourceAuthType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) {
+        FauxtonAPI.addNotification({
+          msg: 'Missing credentials for local source database.',
+          type: 'error',
+          clear: true
+        });
+        return;
+      }
+      if (isLocalTarget && targetAuthType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) {
+        FauxtonAPI.addNotification({
+          msg: 'Missing credentials for local target database.',
+          type: 'error',
+          clear: true
+        });
+        return;
+      }
     }
 
-    this.props.showPasswordModal();
+    this.checkLocalAccountCredentials(sourceAuthType, sourceAuth, 'source', isLocalSource).then(() => {
+      this.checkLocalAccountCredentials(targetAuthType, targetAuth, 'target', isLocalTarget).then(() => {
+        this.submit();
+      }, () => {});
+    }, () => {});
+  }
+
+  checkLocalAccountCredentials(authType, auth, label, isLocal) {
+    // Skip check if it's a remote tb or not using BASIC auth
+    if (authType !== Constants.REPLICATION_AUTH_METHOD.BASIC || !isLocal) {
+      return FauxtonAPI.Promise.resolve(true);
+    }
+
+    if (!auth.username || !auth.password) {
+      const err = `Missing ${label} credentials.`;
+      FauxtonAPI.addNotification({
+        msg: err,
+        type: 'error',
+        clear: true
+      });
+      return FauxtonAPI.Promise.reject(new Error(err));
+    }
+
+    return AuthAPI.login({
+      name: auth.username,
+      password: auth.password
+    }).then((resp) => {
+      if (resp.error) {
+        throw (resp);
+      }
+      return true;
+    }).catch(err => {
+      FauxtonAPI.addNotification({
+        msg: `Your username or password for ${label} database is incorrect.`,
+        type: 'error',
+        clear: true
+      });
+      throw err;
+    });
   }
 
   checkReplicationDocID () {
@@ -64,13 +109,13 @@
         return;
       }
 
-      this.showPasswordModal();
+      this.checkAuth();
     });
   }
 
   runReplicationChecks () {
     const {replicationDocName} = this.props;
-    if (!this.validate()) {
+    if (!this.checkSourceTargetDatabases()) {
       return;
     }
     if (replicationDocName) {
@@ -78,10 +123,10 @@
       return;
     }
 
-    this.showPasswordModal();
+    this.checkAuth();
   }
 
-  validate () {
+  checkSourceTargetDatabases () {
     const {
       remoteTarget,
       remoteSource,
@@ -120,6 +165,48 @@
       }
     }
 
+    //check if remote source/target URL is valid
+    if (!isEmpty(remoteSource)) {
+      let errorMessage = '';
+      try {
+        const url = new URL(remoteSource);
+        if (url.pathname.slice(1) === '') {
+          errorMessage = 'Invalid source database URL. Database name is missing.';
+        }
+      } catch (err) {
+        errorMessage = 'Invalid source database URL.';
+      }
+      if (errorMessage) {
+        FauxtonAPI.addNotification({
+          msg: errorMessage,
+          type: 'error',
+          escape: false,
+          clear: true
+        });
+        return false;
+      }
+    }
+    if (!isEmpty(remoteTarget)) {
+      let errorMessage = '';
+      try {
+        const url = new URL(remoteTarget);
+        if (url.pathname.slice(1) === '') {
+          errorMessage = 'Invalid target database URL. Database name is missing.';
+        }
+      } catch (err) {
+        errorMessage = 'Invalid target database URL.';
+      }
+      if (errorMessage) {
+        FauxtonAPI.addNotification({
+          msg: errorMessage,
+          type: 'error',
+          escape: false,
+          clear: true
+        });
+        return false;
+      }
+    }
+
     //check that source and target are not the same. They can trigger a false positive if they are ""
     if ((remoteTarget === remoteSource && !isEmpty(remoteTarget))
         || (localSource === localTarget && !isEmpty(localSource))) {
@@ -136,7 +223,7 @@
     return true;
   }
 
-  submit (username, password) {
+  submit () {
     const {
       replicationTarget,
       replicationSource,
@@ -145,7 +232,11 @@
       remoteTarget,
       remoteSource,
       localTarget,
-      localSource
+      localSource,
+      sourceAuthType,
+      sourceAuth,
+      targetAuthType,
+      targetAuth
     } = this.props;
 
     let _rev;
@@ -156,19 +247,20 @@
       }
     }
 
-    this.props.hidePasswordModal();
     this.props.replicate({
       replicationTarget,
       replicationSource,
       replicationType,
       replicationDocName,
-      username,
-      password,
       localTarget,
       localSource,
       remoteTarget,
       remoteSource,
-      _rev
+      _rev,
+      sourceAuthType,
+      sourceAuth,
+      targetAuthType,
+      targetAuth
     });
   }
 
@@ -215,7 +307,6 @@
       replicationTarget,
       replicationType,
       replicationDocName,
-      passwordModalVisible,
       conflictModalVisible,
       databases,
       localSource,
@@ -223,11 +314,15 @@
       remoteTarget,
       localTarget,
       updateFormField,
-      clearReplicationForm
+      clearReplicationForm,
+      sourceAuthType,
+      sourceAuth,
+      targetAuthType,
+      targetAuth
     } = this.props;
 
     return (
-      <div>
+      <div style={ {paddingBottom: 20} }>
         <ReplicationSource
           replicationSource={replicationSource}
           localSource={localSource}
@@ -237,6 +332,13 @@
           onRemoteSourceChange={updateFormField('remoteSource')}
           onLocalSourceChange={updateFormField('localSource')}
         />
+        <ReplicationAuth
+          credentials={sourceAuth}
+          authType={sourceAuthType}
+          onChangeAuthType={updateFormField('sourceAuthType')}
+          onChangeAuth={updateFormField('sourceAuth')}
+          authId={'replication-source-auth'}
+        />
         <hr className="replication__seperator" size="1"/>
         <ReplicationTarget
           replicationTarget={replicationTarget}
@@ -247,6 +349,13 @@
           onRemoteTargetChange={updateFormField('remoteTarget')}
           onLocalTargetChange={updateFormField('localTarget')}
         />
+        <ReplicationAuth
+          credentials={targetAuth}
+          authType={targetAuthType}
+          onChangeAuthType={updateFormField('targetAuthType')}
+          onChangeAuth={updateFormField('targetAuth')}
+          authId={'replication-target-auth'}
+        />
         <hr className="replication__seperator" size="1"/>
         <ReplicationOptions
           replicationType={replicationType}
@@ -259,16 +368,9 @@
           onClick={this.runReplicationChecks}
           onClear={clearReplicationForm}
         />
-        <PasswordModal
-          visible={passwordModalVisible}
-          modalMessage={<p>{app.i18n.en_US['replication-password-modal-text']}</p>}
-          submitBtnLabel="Start Replication"
-          headerTitle={app.i18n.en_US['replication-password-modal-header']}
-          onClose={this.props.hidePasswordModal}
-          onSuccess={this.submit} />
         <ConflictModal
           visible={conflictModalVisible}
-          onClick={this.showPasswordModal}
+          onClick={this.checkAuth}
           onClose={this.props.hideConflictModal}
           docId={replicationDocName}
         />
diff --git a/app/addons/replication/components/options.js b/app/addons/replication/components/options.js
index f00ac2e..686413a 100644
--- a/app/addons/replication/components/options.js
+++ b/app/addons/replication/components/options.js
@@ -28,7 +28,7 @@
   return (
     <div className="replication__section">
       <div className="replication__input-label">
-        Replication Type:
+        Replication type:
       </div>
       <div className="replication__input-select">
         <StyledSelect
@@ -49,7 +49,7 @@
 const ReplicationDoc = ({value, onChange}) =>
   <div className="replication__section">
     <div className="replication__input-label">
-    Replication Document:
+    Replication document:
     </div>
     <div className="replication__doc-name">
       <span className="fonticon fonticon-cancel replication__doc-name-icon" title="Clear field"
@@ -76,6 +76,7 @@
 
     return (
       <div>
+        <h3>Options</h3>
         <ReplicationType
           onChange={onTypeChange}
           value={replicationType}
diff --git a/app/addons/replication/components/source.js b/app/addons/replication/components/source.js
index 5b5e8c2..e4b6c33 100644
--- a/app/addons/replication/components/source.js
+++ b/app/addons/replication/components/source.js
@@ -15,7 +15,6 @@
 import Constants from '../constants';
 import Components from '../../components/react-components';
 import ReactSelect from 'react-select';
-import RemoteExample from './remoteexample';
 
 const { StyledSelect } = Components;
 
@@ -30,7 +29,6 @@
         value={value}
         onChange={(e) => onChange(e.target.value)}
       />
-      <RemoteExample />
     </div>
   </div>;
 
@@ -44,7 +42,7 @@
   return (
     <div className="replication__section">
       <div className="replication__input-label">
-        Source Name:
+        Name:
       </div>
       <div className="replication__input-react-select">
         <ReactSelect
@@ -88,7 +86,7 @@
 
 const replicationSourceSelectOptions = () => {
   return [
-    { value: '', label: 'Select source' },
+    { value: '', label: 'Select source type' },
     { value: Constants.REPLICATION_SOURCE.LOCAL, label: 'Local database' },
     { value: Constants.REPLICATION_SOURCE.REMOTE, label: 'Remote database' }
   ].map((option) => {
@@ -103,7 +101,7 @@
   return (
     <div className="replication__section">
       <div className="replication__input-label">
-        Replication Source:
+        Type:
       </div>
       <div className="replication__input-select">
         <StyledSelect
@@ -151,6 +149,7 @@
     const {replicationSource, onSourceSelect} = this.props;
     return (
       <div>
+        <h3>Source</h3>
         <ReplicationSourceSelect
           onChange={onSourceSelect}
           value={replicationSource}
diff --git a/app/addons/replication/components/target.js b/app/addons/replication/components/target.js
index 255bc24..4cfe1c4 100644
--- a/app/addons/replication/components/target.js
+++ b/app/addons/replication/components/target.js
@@ -15,13 +15,12 @@
 import Constants from '../constants';
 import Components from '../../components/react-components';
 import ReactSelect from 'react-select';
-import RemoteExample from './remoteexample';
 
 const { StyledSelect } = Components;
 
 const replicationTargetSourceOptions = () => {
   return [
-    { value: '', label: 'Select target' },
+    { value: '', label: 'Select target type' },
     { value: Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE, label: 'Existing local database' },
     { value: Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE, label: 'Existing remote database' },
     { value: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE, label: 'New local database' },
@@ -37,7 +36,7 @@
   return (
     <div className="replication__section">
       <div className="replication__input-label">
-        Replication Target:
+        Type:
       </div>
       <div id="replication-target" className="replication__input-select">
         <StyledSelect
@@ -55,7 +54,7 @@
   onChange: PropTypes.func.isRequired
 };
 
-const RemoteTargetReplicationRow = ({onChange, value, newRemote}) => {
+const RemoteTargetReplicationRow = ({onChange, value}) => {
   return (
     <div>
       <input
@@ -65,7 +64,6 @@
         value={value}
         onChange={(e) => onChange(e.target.value)}
       />
-      <RemoteExample newRemote={newRemote} />
     </div>
   );
 };
@@ -124,7 +122,7 @@
   let input;
 
   if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
-    targetLabel = 'New Database:';
+    targetLabel = 'New database:';
     input = <NewLocalTargetReplicationRow
       value={localTarget}
       onChange={onLocalTargetChange}
@@ -143,11 +141,11 @@
     />;
   }
 
-  let targetLabel = 'Target Name:';
+  let targetLabel = 'Name:';
 
   if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
       replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
-    targetLabel = 'New Database:';
+    targetLabel = 'New database:';
   }
 
   return (
@@ -183,6 +181,7 @@
     } = this.props;
     return (
       <div>
+        <h3>Target</h3>
         <ReplicationTargetSelect
           value={replicationTarget}
           onChange={onTargetChange}
diff --git a/app/addons/replication/constants.js b/app/addons/replication/constants.js
index b9e699f..46567be 100644
--- a/app/addons/replication/constants.js
+++ b/app/addons/replication/constants.js
@@ -26,5 +26,10 @@
   REPLICATION_TYPE: {
     ONE_TIME: 'REPLICATION_TYPE_ONE_TIME',
     CONTINUOUS: 'REPLICATION_TYPE_CONTINUOUS'
+  },
+
+  REPLICATION_AUTH_METHOD: {
+    NO_AUTH: 'NO_AUTH',
+    BASIC: 'BASIC_AUTH'
   }
 };
diff --git a/app/addons/replication/container.js b/app/addons/replication/container.js
index 7320ae3..b146843 100644
--- a/app/addons/replication/container.js
+++ b/app/addons/replication/container.js
@@ -10,8 +10,6 @@
   getReplicateActivity,
   getReplicationActivity,
   getDatabasesList,
-  showPasswordModal,
-  hidePasswordModal,
   showConflictModal,
   hideConflictModal,
   replicate,
@@ -30,7 +28,6 @@
   isLoading,
   isActivityLoading,
   getDatabases,
-  isAuthenticated,
   getReplicationSource,
   getLocalSource,
   isLocalSourceKnown,
@@ -39,7 +36,6 @@
   getLocalTarget,
   isLocalTargetKnown,
   getRemoteTarget,
-  isPasswordModalVisible,
   isConflictModalVisible,
   getReplicationType,
   getReplicationDocName,
@@ -68,22 +64,24 @@
     loading: isLoading(replication),
     activityLoading: isActivityLoading(replication),
     databases: getDatabases(replication),
-    authenticated: isAuthenticated(replication),
 
     // source fields
     replicationSource: getReplicationSource(replication),
     localSource: getLocalSource(replication),
     localSourceKnown: isLocalSourceKnown(replication),
     remoteSource: getRemoteSource(replication),
+    sourceAuthType: replication.sourceAuthType,
+    sourceAuth: replication.sourceAuth,
 
     // target fields
     replicationTarget: getReplicationTarget(replication),
     localTarget: getLocalTarget(replication),
     localTargetKnown: isLocalTargetKnown(replication),
     remoteTarget: getRemoteTarget(replication),
+    targetAuthType: replication.targetAuthType,
+    targetAuth: replication.targetAuth,
 
     // other
-    passwordModalVisible: isPasswordModalVisible(replication),
     isConflictModalVisible: isConflictModalVisible(replication),
     replicationType: getReplicationType(replication),
     replicationDocName: getReplicationDocName(replication),
@@ -117,8 +115,6 @@
     getReplicateActivity: () => dispatch(getReplicateActivity()),
     getReplicationStateFrom: (id) => dispatch(getReplicationStateFrom(id)),
     getDatabasesList: () => dispatch(getDatabasesList()),
-    showPasswordModal: () => dispatch(showPasswordModal()),
-    hidePasswordModal: () => dispatch(hidePasswordModal()),
     replicate: (params) => dispatch(replicate(params)),
     showConflictModal: () => dispatch(showConflictModal()),
     hideConflictModal: () => dispatch(hideConflictModal()),
diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js
index 117714a..a65febe 100644
--- a/app/addons/replication/controller.js
+++ b/app/addons/replication/controller.js
@@ -62,14 +62,15 @@
   showSection () {
     const {
       replicationSource, replicationTarget, replicationType, replicationDocName,
-      passwordModalVisible, databases, localSource, remoteSource, remoteTarget,
+      databases, localSource, remoteSource, remoteTarget,
       localTarget, statusDocs, statusFilter, loading, allDocsSelected,
       someDocsSelected, showConflictModal, localSourceKnown, localTargetKnown, updateFormField,
-      username, password, authenticated, activityLoading, submittedNoChange, activitySort, tabSection,
+      authenticated, activityLoading, submittedNoChange, activitySort, tabSection,
       replicateInfo, replicateLoading, replicateFilter, allReplicateSelected, someReplicateSelected,
-      showPasswordModal, hidePasswordModal, hideConflictModal, isConflictModalVisible, filterDocs,
+      hideConflictModal, isConflictModalVisible, filterDocs,
       filterReplicate, replicate, clearReplicationForm, selectAllDocs, changeActivitySort, selectDoc,
-      deleteDocs, deleteReplicates, selectAllReplicates, selectReplicate
+      deleteDocs, deleteReplicates, selectAllReplicates, selectReplicate,
+      sourceAuthType, sourceAuth, targetAuthType, targetAuth
     } = this.props;
 
     if (tabSection === 'new replication') {
@@ -83,27 +84,26 @@
         localSourceKnown={localSourceKnown}
         clearReplicationForm={clearReplicationForm}
         replicate={replicate}
-        showPasswordModal={showPasswordModal}
         replicationSource={replicationSource}
         replicationTarget={replicationTarget}
         replicationType={replicationType}
         replicationDocName={replicationDocName}
-        passwordModalVisible={passwordModalVisible}
         databases={databases}
         localSource={localSource}
         remoteSource={remoteSource}
         remoteTarget={remoteTarget}
         localTarget={localTarget}
+        sourceAuthType={sourceAuthType}
+        sourceAuth={sourceAuth}
+        targetAuthType={targetAuthType}
+        targetAuth={targetAuth}
         updateFormField={updateFormField}
         conflictModalVisible={isConflictModalVisible}
         hideConflictModal={hideConflictModal}
         showConflictModal={showConflictModal}
         checkReplicationDocID={checkReplicationDocID}
         authenticated={authenticated}
-        username={username}
-        password={password}
         submittedNoChange={submittedNoChange}
-        hidePasswordModal={hidePasswordModal}
       />;
     }
 
diff --git a/app/addons/replication/reducers.js b/app/addons/replication/reducers.js
index 545e98d..15b6a53 100644
--- a/app/addons/replication/reducers.js
+++ b/app/addons/replication/reducers.js
@@ -9,6 +9,7 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
+import FauxtonAPI from '../../core/api';
 import ActionTypes from './actiontypes';
 import Constants from './constants';
 import app from "../../app";
@@ -41,26 +42,32 @@
   replicationDocName: 'replicationDocName',
   replicationSource: 'replicationSource',
   replicationTarget: 'replicationTarget',
-  localSource: 'localSource'
+  localSource: 'localSource',
+  sourceAuthType: 'sourceAuthType',
+  sourceAuth: 'sourceAuth',
+  targetAuthType: 'targetAuthType',
+  targetAuth: 'targetAuth'
 };
 
 const initialState = {
   loading: false,
   databases: [],
-  authenticated: false,
 
   // source fields
   replicationSource: '',
   localSource: '',
   remoteSource: '',
+  sourceAuthType: Constants.REPLICATION_AUTH_METHOD.NO_AUTH,
+  sourceAuth: {},
 
   // target fields
   replicationTarget: '',
   localTarget: '',
   remoteTarget: '',
+  targetAuthType: Constants.REPLICATION_AUTH_METHOD.NO_AUTH,
+  targetAuth: {},
 
   // other
-  isPasswordModalVisible: false,
   isConflictModalVisible: false,
   replicationType: Constants.REPLICATION_TYPE.ONE_TIME,
   replicationDocName: '',
@@ -87,7 +94,15 @@
   const newState = {
     ...state
   };
-  Object.values(validFieldMap).forEach(field => newState[field] = '');
+  Object.values(validFieldMap).forEach(field => {
+    if (field === 'sourceAuth' || field === 'targetAuth') {
+      newState[field] = {};
+    } else {
+      newState[field] = '';
+    }
+  });
+  newState.sourceAuthType = Constants.REPLICATION_AUTH_METHOD.NO_AUTH;
+  newState.targetAuthType = Constants.REPLICATION_AUTH_METHOD.NO_AUTH;
   return newState;
 };
 
@@ -96,9 +111,32 @@
     ...state,
     submittedNoChange: false,
   };
-
   updateState[validFieldMap[fieldName]] = value;
 
+  // Set default username when state is set to local target/source AND auth is user/pwd
+  if (fieldName === validFieldMap.sourceAuthType || fieldName === validFieldMap.replicationSource) {
+    const isUserPwdAuth = updateState[validFieldMap.sourceAuthType] === Constants.REPLICATION_AUTH_METHOD.BASIC;
+    const isLocalDB = updateState[validFieldMap.replicationSource] === Constants.REPLICATION_SOURCE.LOCAL;
+    const usernameNotSet = !updateState[validFieldMap.sourceAuth] || !updateState[validFieldMap.sourceAuth].username;
+    if (isUserPwdAuth && isLocalDB && usernameNotSet) {
+      updateState[validFieldMap.sourceAuth] = {
+        username: FauxtonAPI.session.user().name,
+        password: ''
+      };
+    }
+  } else if (fieldName === validFieldMap.targetAuthType || fieldName === validFieldMap.replicationTarget) {
+    const isUserPwdAuth = updateState[validFieldMap.targetAuthType] === Constants.REPLICATION_AUTH_METHOD.BASIC;
+    const isLocalDB = updateState[validFieldMap.replicationTarget] === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE ||
+      updateState[validFieldMap.replicationTarget] === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE;
+    const usernameNotSet = !updateState[validFieldMap.targetAuth] || !updateState[validFieldMap.targetAuth].username;
+    if (isUserPwdAuth && isLocalDB && usernameNotSet) {
+      updateState[validFieldMap.targetAuth] = {
+        username: FauxtonAPI.session.user().name,
+        password: ''
+      };
+    }
+  }
+
   return updateState;
 };
 
@@ -306,18 +344,6 @@
         allReplicateSelected: false
       };
 
-    case ActionTypes.REPLICATION_SHOW_PASSWORD_MODAL:
-      return {
-        ...state,
-        isPasswordModalVisible: true
-      };
-
-    case ActionTypes.REPLICATION_HIDE_PASSWORD_MODAL:
-      return {
-        ...state,
-        isPasswordModalVisible: false
-      };
-
     default:
       return state;
   }
@@ -327,7 +353,6 @@
 export const isLoading = (state) => state.isLoading;
 export const isActivityLoading = (state) => state.activityLoading;
 export const getDatabases = (state) => state.databases;
-export const isAuthenticated = (state) => state.authenticated;
 
 export const getReplicationSource = (state) => state.replicationSource;
 export const getLocalSource = (state) => state.localSource;
@@ -340,7 +365,6 @@
 export const isLocalTargetKnown = (state) => _.includes(state.databases, state.localTarget);
 export const getRemoteTarget = (state) => state.remoteTarget;
 
-export const isPasswordModalVisible = (state) => state.isPasswordModalVisible;
 export const isConflictModalVisible = (state) => state.isConflictModalVisible;
 export const getReplicationType = (state) => state.replicationType;
 export const getReplicationDocName = (state) => state.replicationDocName;
diff --git a/app/addons/replication/tests/nightwatch/replication.js b/app/addons/replication/tests/nightwatch/replication.js
index bd0c4a4..0d2551f 100644
--- a/app/addons/replication/tests/nightwatch/replication.js
+++ b/app/addons/replication/tests/nightwatch/replication.js
@@ -57,22 +57,33 @@
       // enter our source DB
       .setValue('.replication__input-react-select .Select-input input', [newDatabaseName1, client.Keys.ENTER])
 
+      // select source USER/PASSWORD authentication
+      .clickWhenVisible('#select-replication-source-auth')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('#replication-source-auth-username', waitTime, true)
+
+      // enter source username/password
+      .setValue('#replication-source-auth-password', [password, client.Keys.ENTER])
+
       // enter a new target name
       .waitForElementVisible('#replication-target', waitTime, true)
       .clickWhenVisible('option[value="REPLICATION_TARGET_NEW_LOCAL_DATABASE"]')
       .setValue('.replication__new-input', replicatedDBName)
 
+      // select target USER/PASSWORD authentication
+      .clickWhenVisible('#select-replication-target-auth')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('#replication-target-auth-username', waitTime, true)
+
+      // enter target username/password
+      .setValue('#replication-target-auth-password', [password, client.Keys.ENTER])
+
       .clickWhenVisible('#replicate')
 
-      .waitForElementVisible('.enter-password-modal', waitTime, true)
-      .setValue('.enter-password-modal .password-modal-input', password)
-      .clickWhenVisible('.enter-password-modal button.save')
-      .waitForElementNotPresent('.enter-password-modal', waitTime, true)
       .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
       .end();
   },
 
-
   'Replicates existing local db to existing local db' : function (client) {
     const waitTime = client.globals.maxWaitTime;
     const baseUrl = client.globals.test_settings.launch_url;
@@ -100,20 +111,34 @@
       .waitForElementVisible('.replication__input-react-select', waitTime, true)
       .setValue('.replication__input-react-select .Select-input input', [newDatabaseName1, client.Keys.ENTER])
 
+      // select source USER/PASSWORD authentication
+      .clickWhenVisible('#select-replication-source-auth')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('#replication-source-auth-username', waitTime, true)
+
+      // enter source username/password
+      .setValue('#replication-source-auth-password', [password, client.Keys.ENTER])
+
       // select existing local as the target
       .waitForElementVisible('#replication-target', waitTime, true)
       .clickWhenVisible('#replication-target option[value="REPLICATION_TARGET_EXISTING_LOCAL_DATABASE"]')
       .setValue('#replication-target-local .Select-input input', [newDatabaseName2, client.Keys.ENTER])
 
+      // select target USER/PASSWORD authentication
+      .clickWhenVisible('#select-replication-target-auth')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('#replication-target-auth-username', waitTime, true)
+
+      // enter target username/password
+      .setValue('#replication-target-auth-password', [password, client.Keys.ENTER])
+
       .getAttribute('#replicate', 'disabled', function (result) {
         // confirm it's not disabled
         this.assert.equal(result.value, null);
       })
       .clickWhenVisible('#replicate')
 
-      .waitForElementVisible('.enter-password-modal', waitTime, true)
-      .setValue('.enter-password-modal input[type="password"]', password)
-      .clickWhenVisible('.enter-password-modal button.save')
+      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
       .end();
   },
 
@@ -151,23 +176,111 @@
       .waitForElementVisible('.replication__input-react-select', waitTime, true)
       .setValue('.replication__input-react-select .Select-input input', [newDatabaseName1, client.Keys.ENTER])
 
+      // select source USER/PASSWORD authentication
+      .clickWhenVisible('#select-replication-source-auth')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('#replication-source-auth-username', waitTime, true)
+
+      // enter source username/password
+      .setValue('#replication-source-auth-password', [password, client.Keys.ENTER])
+
       // select existing local as the target
       .waitForElementVisible('#replication-target', waitTime, true)
       .clickWhenVisible('#replication-target option[value="REPLICATION_TARGET_EXISTING_LOCAL_DATABASE"]')
       .setValue('#replication-target-local .Select-input input', [newDatabaseName2, client.Keys.ENTER])
       .setValue('.replication__doc-name-input', [replicatorDoc._id, client.Keys.ENTER])
 
+      // select target USER/PASSWORD authentication
+      .clickWhenVisible('#select-replication-target-auth')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('#replication-target-auth-username', waitTime, true)
+
+      // enter target username/password
+      .setValue('#replication-target-auth-password', [password, client.Keys.ENTER])
+
       .getAttribute('#replicate', 'disabled', function (result) {
         // confirm it's not disabled
         this.assert.equal(result.value, null);
       })
       .clickWhenVisible('#replicate')
 
+      // confirm overwrite of existing doc
       .waitForElementVisible('.replication__error-doc-modal .replication__error-continue', waitTime, true)
       .clickWhenVisible('.replication__error-doc-modal .replication__error-continue')
-      .waitForElementVisible('.enter-password-modal', waitTime, true)
-      .setValue('.enter-password-modal input[type="password"]', password)
-      .clickWhenVisible('.enter-password-modal button.save')
+
+      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .end();
+  },
+
+  'Show error for missing credentials' : function (client) {
+    const waitTime = client.globals.maxWaitTime;
+    const baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .createDatabase(newDatabaseName1)
+      .checkForDatabaseCreated(newDatabaseName1, waitTime)
+      .createDocument(docName1, newDatabaseName1)
+      .loginToGUI()
+      .url(baseUrl + '/#/replication/_create')
+      .waitForElementVisible('button#replicate', waitTime, true)
+      .waitForElementVisible('#replication-source', waitTime, true)
+
+      // select LOCAL as the source
+      .clickWhenVisible('#replication-source')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('.replication__input-react-select', waitTime, true)
+
+      // enter our source DB
+      .setValue('.replication__input-react-select .Select-input input', [newDatabaseName1, client.Keys.ENTER])
+
+      // enter a new target name
+      .waitForElementVisible('#replication-target', waitTime, true)
+      .clickWhenVisible('option[value="REPLICATION_TARGET_NEW_LOCAL_DATABASE"]')
+      .setValue('.replication__new-input', replicatedDBName)
+
+      .clickWhenVisible('#replicate')
+
+      .waitForElementPresent('.global-notification.alert-error', waitTime, true)
+      .end();
+  },
+
+  'Show error for invalid credentials' : function (client) {
+    const waitTime = client.globals.maxWaitTime;
+    const baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .createDatabase(newDatabaseName1)
+      .checkForDatabaseCreated(newDatabaseName1, waitTime)
+      .createDocument(docName1, newDatabaseName1)
+      .loginToGUI()
+      .url(baseUrl + '/#/replication/_create')
+      .waitForElementVisible('button#replicate', waitTime, true)
+      .waitForElementVisible('#replication-source', waitTime, true)
+
+      // select LOCAL as the source
+      .clickWhenVisible('#replication-source')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('.replication__input-react-select', waitTime, true)
+
+      // enter our source DB
+      .setValue('.replication__input-react-select .Select-input input', [newDatabaseName1, client.Keys.ENTER])
+
+      // select source USER/PASSWORD authentication
+      .clickWhenVisible('#select-replication-source-auth')
+      .keys(['\uE015', '\uE006'])
+      .waitForElementVisible('#replication-source-auth-username', waitTime, true)
+
+      // enter source username/password
+      .setValue('#replication-source-auth-password', ['wrong_pwd', client.Keys.ENTER])
+
+      // enter a new target name
+      .waitForElementVisible('#replication-target', waitTime, true)
+      .clickWhenVisible('option[value="REPLICATION_TARGET_NEW_REMOTE_DATABASE"]')
+      .setValue('.replication__remote-connection-url', 'http://fake.com/dummydb')
+
+      .clickWhenVisible('#replicate')
+
+      .waitForElementPresent('.global-notification.alert-error', waitTime, true)
       .end();
   }
 };
diff --git a/i18n.json.default.json b/i18n.json.default.json
index f5baf56..d104dcc 100644
--- a/i18n.json.default.json
+++ b/i18n.json.default.json
@@ -13,6 +13,9 @@
     "cors-notice": "Cross-Origin Resource Sharing (CORS) lets you connect to remote servers directly from the browser, so you can host browser-based apps on static pages and talk directly with CouchDB to load your data.",
     "replication-password-modal-header": "Enter Account Password.",
     "replication-password-modal-text": "Replication requires authentication on your credentials.",
+    "replication-user-password-auth-label": "Username and password",
+    "replication-username-input-placeholder": "Username",
+    "replication-password-input-placeholder": "Password",
     "auth-missing-credentials": "Username or password cannot be blank.",
     "auth-logged-in": "You have been logged in.",
     "auth-admin-created": "CouchDB admin created",