Page MenuHomePhabricator

Allow multiple copies of the same auth provider type
Open, WishlistPublic

Assigned To
Authored By
Dec 6 2014, 3:30 PM
Referenced Files
"Like" token, awarded by robertos."Like" token, awarded by friesenschrauber."Like" token, awarded by ofbeaton."Love" token, awarded by Krenair.


See T887. Some auth providers could theoretically support multiple instances on a single install: for example, you might have two different LDAP servers you want to authenticate against, or two different other Phabricator instances, or GitHub (public) and GitHub (enterprise), or multiple JIRA instances, or multiple MediaWiki instances.

These use cases are very rare, but there is no technical reason to prohibit them, and they would make some tasks (like integrating with multiple JIRA instances) easier.

Related Objects

Mentioned In
T13493: JIRA API has changed identifiers from "key" to "accountId"
T524: OpenID Authentication as an Auth Option
D20206: Fix Facebook login on mobile violating CSP after form redirect
T7732: Convoluted flow when locked out of account with only one auth provider
rP9632c704c69d: Always allow users to login via email link, even if an install does not use…
D20100: Always allow users to login via email link, even if an install does not use passwords
T2549: Support linking multiple external accounts from the same provider with one Phabricator account
rP1d0b99e1f834: Allow applications to require a High Security token without doing a session…
T13222: 2018 Week 48-51 Bonus Content
T13190: Plans: User Accounts, Profiles, Registration and Imports
T12288: Phabricator OAuth 2.0 initiating SSO login from a Third Party website
T11980: Links to external systems
T11514: Authentication should have a way to customize the credentials name
Q352: multiple domains for authentication providers (Answer 352)
T6707: 'Attaching' two external accounts of the same type results in AphrontCountQueryException
Mentioned Here
T13493: JIRA API has changed identifiers from "key" to "accountId"
T13250: Some typeaheads use nonscalar HTTP parameters, which fatal under new "phutil_build_http_query()" rules
D20108: Remove weird integration between Legalpad and the ExternalAccount table
D20112: Give ExternalAccount a providerConfigPHID, tying it to a particular provider
D20113: Make external account unlinking use account IDs, not "providerType + providerDomain" nonsense
D20117: Make external link/refresh use provider IDs, switch external account MFA to one-shot
D20105: In "External Accounts", replace hard-to-find tiny "link" icon with a nice button with text on it
T2549: Support linking multiple external accounts from the same provider with one Phabricator account

Event Timeline

epriestley raised the priority of this task from to Wishlist.
epriestley updated the task description. (Show Details)
epriestley added a project: Auth.
epriestley added a subscriber: epriestley.

For what is worth, we have the case of users with two Wikimedia accounts, and some of them do try to attach them to the same Phabricator account. The result is 'Attaching' two mediawiki accounts results in AphrontCountQueryException.

@qgil That's T2549, but the exception is not expected behavior. If you want to file that, we can fix it -- it's supposed to prevent attaching a second account until we support multiple accounts properly.

Are there any plans already for implementing this feature some time soon?

Came here to request this feature, searched for it, found it. Multiple LDAPs over here. Is there some kind of +1 technology over here so you know how many people come here to request it?

I can say that this feature would be very nice. I have two LDAP servers in my case and other tools/services that I use allow me to configure both (whether as primary/backup or round-robin). It would be nice if Phabricator allowed specifying multiple LDAP server host names. Of course, I tried that before adding this comment got this error:

[2017-08-21 22:00:41] EXCEPTION: (Exception) Unable to connect to LDAP server (, at [<phutil>/src/auth/PhutilLDAPAuthAdapter.php:317]
arcanist(head=master, ref.master=165df12046e5), phabricator(head=master, ref.master=bddd1da053e8), phutil(head=master, ref.master=0a4487d37cd7)
  #0 PhutilLDAPAuthAdapter::establishConnection() called at [<phutil>/src/auth/PhutilLDAPAuthAdapter.php:213]
  #1 PhutilLDAPAuthAdapter::loadLDAPUserData() called at [<phutil>/src/auth/PhutilLDAPAuthAdapter.php:161]
  #2 PhutilLDAPAuthAdapter::getLDAPUserData() called at [<phutil>/src/auth/PhutilLDAPAuthAdapter.php:114]
  #3 PhutilLDAPAuthAdapter::getAccountID() called at [<phabricator>/src/applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php:59]
  #4 PhabricatorAuthManagementLDAPWorkflow::execute(PhutilArgumentParser) called at [<phutil>/src/parser/argument/PhutilArgumentParser.php:441]
  #5 PhutilArgumentParser::parseWorkflowsFull(array) called at [<phutil>/src/parser/argument/PhutilArgumentParser.php:333]
  #6 PhutilArgumentParser::parseWorkflows(array) called at [<phabricator>/scripts/setup/manage_auth.php:21]

I tried a space separator as well and received the same error. A look at the code in PhutilLDAPAuthAdapter.php confirmed that it expects s single host name only in the hostname field.

The multiple copies of the auth provider would work of course, but I think that allowing multiple hosts in the LDAP auth provider (and perhaps other providers that require a remote host name) might be a bit easier to implement. Anyhow, those are my thoughts, for what they are worth.

Here are some general technical blockers here:

  • ExternalAccount does not have an authProviderConfigPHID. It is bound to a particular provider by the combination of accountType plus accountDomain matching the "providerKey", which is the providerType plus providerDomain.
  • A lot of controllers use the "providerKey" to identify a provider. I'm gradually fixing these to use a ProviderConfig ID or PHID.
  • Legalpad uses ExternalAccount to store arbitrary email addresses as an identity. Nuance and old (or current?) versions of Maniphest inbound mail handling may do something similar.

There is some amount of theoretical merit to this approach. The idea is that if some Twitter account interacts with Nuance and a user later links their Phabricator account to that Twitter account, it may be useful to know that the two are the same. However, I think we should probably separate these ideas and reconcile them later, in the application, so we can treat ExternalAccount as "External Authentication Account" not "External Account, Sometimes for Authentication, Sometimes Just A Thing We Know About".

  • There is some merit to enforcing unique constraints on the ExternalAccount table. In particular, a world where you add two Google providers and link the same Google identity to two different Phabricator identities by linking it once under each provider is a recipe for bad times, sort of. This doesn't necessarily bring about the end of the world on its own, since you'd still have two login buttons and we could figure out which account you should hit depending on which one you clicked. This is still probably fairly confusing. We can enforce uniqueness separately in the ExternalAccount table by letting each account provide a uniqueness key. Google can say (global uniqueness across all Google providers) and LDAP can say my-provider-phid:account-id (uniqueness only across a given provider) since it's okay to have two different accounts with the same account ID on two different LDAP providers.

(Misfire in the plain text of D20105.)

Legalpad uses ExternalAccount to store arbitrary email addresses as an identity.

Resolved by D20108.

ExternalAccount does not have an authProviderConfigPHID.

Resolved by D20112.

A lot of controllers use the "providerKey" to identify a provider.

This is resolved in most places (in D20113, D20117, and elsewhere).

The remaining case is that OAuth redirection uses /login/ or similar. This is slightly tricky because I believe we can't change these links in the general case without breaking OAuth configuration: some OAuth providers are configured with a whitelist of specific redirect URIs (which is good), not just redirect domains (which were more common in older provider implementations).

To support multiple providers of the same type we want this URI to look like /login/with-provider/123/ or similar. But we can store the stuff in some "legacyKey" column and survive mostly unscathed.

I'm going to dump everything I've got into master now that T13250 is mostly stabilized so it at least gets a couple of days of testing. I don't think I'm breaking anything in rP, but instances/ may require changes to instance initialization before this stuff can deploy.

For Phacility impact on the ExternalAccount changes so far, these callsites don't seem impacted yet (but might be worth keeping in mind in the future):

  • When you log out of an instance, we also log you out of your central account. This isn't currently affected by any of the changes.
  • When we wipe an imported instance, we wipe user_externalaccount completely. This is just a TRUNCATE, so no impact so far.
  • When you export instance data, we wipe user_externalaccount completely. This is also a TRUNCATE that doesn't need to change.

These cases are impacted:

  • In services/, bin/services sync, which is executed by the daemons as part of instance startup, writes directly to the central ExternalAccount table to link your instance administrator account to your central account. This needs a change to also write the correct providerConfigPHID.
  • In instances/, writeExternalAccountLink() also writes directly to the ExternalAccount table and needs a change. This flow happens when we import external instance data, then invite existing users to claim existing accounts, and then they actually claim those accounts.
epriestley added a revision: Restricted Differential Revision.Feb 14 2019, 12:05 PM
epriestley added a revision: Restricted Differential Revision.Feb 14 2019, 12:29 PM
epriestley added a commit: Restricted Diffusion Commit.Feb 15 2019, 11:03 PM

Couple of notes on the state of affairs here:

  • After T13493, accountType, accountDomain, and accountID on ExternalAccount no longer have any readers or nontrivial writers. All three columns can be removed once the dust settles without issue.