diff --git a/resources/sql/autopatches/20200220.xaccount.01.sql b/resources/sql/autopatches/20200220.xaccount.01.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20200220.xaccount.01.sql
@@ -0,0 +1,10 @@
+CREATE TABLE {$NAMESPACE}_user.user_externalaccountidentifier (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  phid VARBINARY(64) NOT NULL,
+  externalAccountPHID VARBINARY(64) NOT NULL,
+  providerConfigPHID VARBINARY(64) NOT NULL,
+  identifierHash BINARY(12) NOT NULL,
+  identifierRaw LONGTEXT NOT NULL,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -3318,6 +3318,8 @@
     'PhabricatorExtendingPhabricatorConfigOptions' => 'applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php',
     'PhabricatorExtensionsSetupCheck' => 'applications/config/check/PhabricatorExtensionsSetupCheck.php',
     'PhabricatorExternalAccount' => 'applications/people/storage/PhabricatorExternalAccount.php',
+    'PhabricatorExternalAccountIdentifier' => 'applications/people/storage/PhabricatorExternalAccountIdentifier.php',
+    'PhabricatorExternalAccountIdentifierQuery' => 'applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php',
     'PhabricatorExternalAccountQuery' => 'applications/auth/query/PhabricatorExternalAccountQuery.php',
     'PhabricatorExternalAccountsSettingsPanel' => 'applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php',
     'PhabricatorExtraConfigSetupCheck' => 'applications/config/check/PhabricatorExtraConfigSetupCheck.php',
@@ -4103,6 +4105,7 @@
     'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php',
     'PhabricatorPeopleEmailLoginMailEngine' => 'applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php',
     'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php',
+    'PhabricatorPeopleExternalIdentifierPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalIdentifierPHIDType.php',
     'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php',
     'PhabricatorPeopleIconSet' => 'applications/people/icon/PhabricatorPeopleIconSet.php',
     'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php',
@@ -9763,6 +9766,11 @@
       'PhabricatorUserDAO',
       'PhabricatorPolicyInterface',
     ),
+    'PhabricatorExternalAccountIdentifier' => array(
+      'PhabricatorUserDAO',
+      'PhabricatorPolicyInterface',
+    ),
+    'PhabricatorExternalAccountIdentifierQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorExternalAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhabricatorExternalAccountsSettingsPanel' => 'PhabricatorSettingsPanel',
     'PhabricatorExtraConfigSetupCheck' => 'PhabricatorSetupCheck',
@@ -10681,6 +10689,7 @@
     'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleEmailLoginMailEngine' => 'PhabricatorPeopleMailEngine',
     'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController',
+    'PhabricatorPeopleExternalIdentifierPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorPeopleIconSet' => 'PhabricatorIconSet',
     'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController',
diff --git a/src/applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php b/src/applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php
@@ -0,0 +1,94 @@
+<?php
+
+final class PhabricatorExternalAccountIdentifierQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $phids;
+  private $providerConfigPHIDs;
+  private $externalAccountPHIDs;
+  private $rawIdentifiers;
+
+  public function withIDs($ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withPHIDs(array $phids) {
+    $this->phids = $phids;
+    return $this;
+  }
+
+  public function withProviderConfigPHIDs(array $phids) {
+    $this->providerConfigPHIDs = $phids;
+    return $this;
+  }
+
+  public function withExternalAccountPHIDs(array $phids) {
+    $this->externalAccountPHIDs = $phids;
+    return $this;
+  }
+
+  public function withRawIdentifiers(array $identifiers) {
+    $this->rawIdentifiers = $identifiers;
+    return $this;
+  }
+
+  public function newResultObject() {
+    return new PhabricatorExternalAccountIdentifier();
+  }
+
+  protected function loadPage() {
+    return $this->loadStandardPage($this->newResultObject());
+  }
+
+  protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+    $where = parent::buildWhereClauseParts($conn);
+
+    if ($this->ids !== null) {
+      $where[] = qsprintf(
+        $conn,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->phids !== null) {
+      $where[] = qsprintf(
+        $conn,
+        'phid IN (%Ls)',
+        $this->phids);
+    }
+
+    if ($this->providerConfigPHIDs !== null) {
+      $where[] = qsprintf(
+        $conn,
+        'providerConfigPHID IN (%Ls)',
+        $this->providerConfigPHIDs);
+    }
+
+    if ($this->externalAccountPHIDs !== null) {
+      $where[] = qsprintf(
+        $conn,
+        'externalAccountPHID IN (%Ls)',
+        $this->externalAccountPHIDs);
+    }
+
+    if ($this->rawIdentifiers !== null) {
+      $hashes = array();
+      foreach ($this->rawIdentifiers as $raw_identifier) {
+        $hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
+      }
+      $where[] = qsprintf(
+        $conn,
+        'identifierHash IN (%Ls)',
+        $hashes);
+    }
+
+    return $where;
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorPeopleApplication';
+  }
+
+}
diff --git a/src/applications/people/phid/PhabricatorPeopleExternalIdentifierPHIDType.php b/src/applications/people/phid/PhabricatorPeopleExternalIdentifierPHIDType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/people/phid/PhabricatorPeopleExternalIdentifierPHIDType.php
@@ -0,0 +1,38 @@
+<?php
+
+final class PhabricatorPeopleExternalIdentifierPHIDType
+  extends PhabricatorPHIDType {
+
+  const TYPECONST = 'XIDT';
+
+  public function getTypeName() {
+    return pht('External Account Identifier');
+  }
+
+  public function newObject() {
+    return new PhabricatorExternalAccountIdentifier();
+  }
+
+  public function getPHIDTypeApplicationClass() {
+    return 'PhabricatorPeopleApplication';
+  }
+
+  protected function buildQueryForObjects(
+    PhabricatorObjectQuery $query,
+    array $phids) {
+
+    return id(new PhabricatorExternalAccountIdentifierQuery())
+      ->withPHIDs($phids);
+  }
+
+  public function loadHandles(
+    PhabricatorHandleQuery $query,
+    array $handles,
+    array $objects) {
+
+    foreach ($handles as $phid => $handle) {
+      $identifier = $objects[$phid];
+    }
+  }
+
+}
diff --git a/src/applications/people/storage/PhabricatorExternalAccountIdentifier.php b/src/applications/people/storage/PhabricatorExternalAccountIdentifier.php
new file mode 100644
--- /dev/null
+++ b/src/applications/people/storage/PhabricatorExternalAccountIdentifier.php
@@ -0,0 +1,67 @@
+<?php
+
+final class PhabricatorExternalAccountIdentifier
+  extends PhabricatorUserDAO
+  implements PhabricatorPolicyInterface {
+
+  protected $externalAccountPHID;
+  protected $providerConfigPHID;
+  protected $identifierHash;
+  protected $identifierRaw;
+
+  public function getPHIDType() {
+    return PhabricatorPeopleExternalIdentifierPHIDType::TYPECONST;
+  }
+
+  protected function getConfiguration() {
+    return array(
+      self::CONFIG_AUX_PHID => true,
+      self::CONFIG_COLUMN_SCHEMA => array(
+        'identifierHash' => 'bytes12',
+        'identifierRaw' => 'text',
+      ),
+      self::CONFIG_KEY_SCHEMA => array(
+        'key_identifier' => array(
+          'columns' => array('providerConfigPHID', 'identifierHash'),
+          'unique' => true,
+        ),
+        'key_account' => array(
+          'columns' => array('externalAccountPHID'),
+        ),
+      ),
+    ) + parent::getConfiguration();
+  }
+
+  public function save() {
+    $identifier_raw = $this->getIdentifierRaw();
+    $this->identiferHash = PhabricatorHash::digestForIndex($identifier_raw);
+    return parent::save();
+  }
+
+
+/* -(  PhabricatorPolicyInterface  )----------------------------------------- */
+
+  // TODO: These permissions aren't very good. They should just be the same
+  // as the associated ExternalAccount. See T13381.
+
+  public function getCapabilities() {
+    return array(
+      PhabricatorPolicyCapability::CAN_VIEW,
+      PhabricatorPolicyCapability::CAN_EDIT,
+    );
+  }
+
+  public function getPolicy($capability) {
+    switch ($capability) {
+      case PhabricatorPolicyCapability::CAN_VIEW:
+        return PhabricatorPolicies::getMostOpenPolicy();
+      case PhabricatorPolicyCapability::CAN_EDIT:
+        return PhabricatorPolicies::POLICY_NOONE;
+    }
+  }
+
+  public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+    return false;
+  }
+
+}