diff --git a/CHANGELOG b/CHANGELOG
index b827b7c69ebe16a2e5424af56b35ed74c79406e0..bbf44ca14c902f0091e503896032fd07bdaafbb9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -24,6 +24,7 @@ v 8.0.0 (unreleased)
   - Improve search page usability
   - Bring more UI consistency in way how projects, snippets and groups lists are rendered
   - Make all profiles public
+  - Fixed login failure when extern_uid changes (Joel Koglin)
 
 v 7.14.1
   - Improve abuse reports management from admin area
diff --git a/app/models/user.rb b/app/models/user.rb
index b4e779370a0e8e9a0b03cb3440554d423b497eb9..48e0e6ed48bb6aba4e1af7219ce3a511dd52258c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -104,7 +104,7 @@ class User < ActiveRecord::Base
   # Profile
   has_many :keys, dependent: :destroy
   has_many :emails, dependent: :destroy
-  has_many :identities, dependent: :destroy
+  has_many :identities, dependent: :destroy, autosave: true
 
   # Groups
   has_many :members, dependent: :destroy
diff --git a/db/migrate/20150817163600_deduplicate_user_identities.rb b/db/migrate/20150817163600_deduplicate_user_identities.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fab669c2905c4be69bd7d4504fd0a099bb18b0c9
--- /dev/null
+++ b/db/migrate/20150817163600_deduplicate_user_identities.rb
@@ -0,0 +1,14 @@
+class DeduplicateUserIdentities < ActiveRecord::Migration
+  def change
+    execute 'DROP TABLE IF EXISTS tt_migration_DeduplicateUserIdentities;'
+    execute 'CREATE TEMPORARY TABLE tt_migration_DeduplicateUserIdentities AS SELECT id,provider,user_id FROM identities;'
+    execute 'DELETE FROM identities WHERE id NOT IN ( SELECT MIN(id) FROM tt_migration_DeduplicateUserIdentities GROUP BY user_id, provider);'
+    execute 'DROP TABLE IF EXISTS tt_migration_DeduplicateUserIdentities;'
+  end
+
+  def down
+    # This is an irreversible migration;
+    # If someone is trying to rollback for other reasons, we should not throw an Exception.
+    # raise ActiveRecord::IrreversibleMigration
+  end
+end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index f7f3ba9ad7d3322b4afb50215224de2bdb27c928..04a2223747828987bc10b302624897fa5a89d6bd 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -44,9 +44,14 @@ module Gitlab
         gl_user.skip_reconfirmation!
         gl_user.email = auth_hash.email
 
-        # Build new identity only if we dont have have same one
-        gl_user.identities.find_or_initialize_by(provider: auth_hash.provider,
-                                                 extern_uid: auth_hash.uid)
+        # find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved.
+        identity = gl_user.identities.find { |identity|  identity.provider == auth_hash.provider }
+        identity ||= gl_user.identities.build(provider: auth_hash.provider)
+        
+        # For a new user set extern_uid to the LDAP DN
+        # For an existing user with matching email but changed DN, update the DN.
+        # For an existing user with no change in DN, this line changes nothing.
+        identity.extern_uid = auth_hash.uid
 
         gl_user
       end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 7cfca96f4e03d0b776614a17ddaf10a396177fb2..84d9fb54b615fee2ad102e1a41dd1dd43f1f5932 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -47,6 +47,28 @@ describe Gitlab::LDAP::User do
       expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
     end
 
+    it 'connects to existing ldap user if the extern_uid changes' do
+      existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
+      expect{ ldap_user.save }.not_to change{ User.count }
+
+      existing_user.reload
+      expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+      expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
+      expect(existing_user.id).to eql ldap_user.gl_user.id
+    end
+
+    it 'maintains an identity per provider' do
+      existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter')
+      expect(existing_user.identities.count).to eql(1)
+
+      ldap_user.save
+      expect(ldap_user.gl_user.identities.count).to eql(2)
+
+      # Expect that find_by provider only returns a single instance of an identity and not an Enumerable
+      expect(ldap_user.gl_user.identities.find_by(provider: 'twitter')).to be_instance_of Identity
+      expect(ldap_user.gl_user.identities.find_by(provider: auth_hash.provider)).to be_instance_of Identity
+    end
+
     it "creates a new user if not found" do
       expect{ ldap_user.save }.to change{ User.count }.by(1)
     end