diff --git a/Gemfile b/Gemfile
index a50d7e632aef86360a11e418b11eae3a4309886b..1e7c7869cba9b08cad3048f11fec95a8dd671717 100644
--- a/Gemfile
+++ b/Gemfile
@@ -45,6 +45,7 @@ gem 'akismet', '~> 2.0'
 gem 'devise-two-factor', '~> 3.0.0'
 gem 'rqrcode-rails3', '~> 0.1.7'
 gem 'attr_encrypted', '~> 3.0.0'
+gem 'u2f', '~> 0.2.1'
 
 # Browser detection
 gem "browser", '~> 1.0.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1771b919b60ec679aa566e1c8dc018760e2df2b9..bdf7ab97746d176dea06e452e3331161c0e9325e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -747,6 +747,7 @@ GEM
       simple_oauth (~> 0.1.4)
     tzinfo (1.2.2)
       thread_safe (~> 0.1)
+    u2f (0.2.1)
     uglifier (2.7.2)
       execjs (>= 0.3.0)
       json (>= 1.8.0)
@@ -963,6 +964,7 @@ DEPENDENCIES
   thin (~> 1.6.1)
   tinder (~> 1.10.0)
   turbolinks (~> 2.5.0)
+  u2f (~> 0.2.1)
   uglifier (~> 2.7.2)
   underscore-rails (~> 1.8.0)
   unf (~> 0.1.4)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c28d1ca9e3bf0102453c94239f28ccc450480905..e73b2d085518378de74fef4f50738aea6ad6e01b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -182,8 +182,8 @@ class ApplicationController < ActionController::Base
   end
 
   def check_2fa_requirement
-    if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
-      redirect_to new_profile_two_factor_auth_path
+    if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled? && !skip_two_factor?
+      redirect_to profile_two_factor_auth_path
     end
   end
 
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index b05fa0a14d6c8849b772b71f3d4605c49bd9ffff..cd4d778e508252b1e3e9b4508ac5af171fa9a120 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -66,7 +66,7 @@ module AuthHelper
 
   def two_factor_skippable?
     current_application_settings.require_two_factor_authentication &&
-      !current_user.two_factor_enabled &&
+      !current_user.two_factor_enabled? &&
       current_application_settings.two_factor_grace_period &&
       !two_factor_grace_period_expired?
   end
diff --git a/app/models/u2f_registration.rb b/app/models/u2f_registration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..00b19686d482e985c1362f1c4ab9cf258fad754f
--- /dev/null
+++ b/app/models/u2f_registration.rb
@@ -0,0 +1,40 @@
+# Registration information for U2F (universal 2nd factor) devices, like Yubikeys
+
+class U2fRegistration < ActiveRecord::Base
+  belongs_to :user
+
+  def self.register(user, app_id, json_response, challenges)
+    u2f = U2F::U2F.new(app_id)
+    registration = self.new
+
+    begin
+      response = U2F::RegisterResponse.load_from_json(json_response)
+      registration_data = u2f.register!(challenges, response)
+      registration.update(certificate: registration_data.certificate,
+                          key_handle: registration_data.key_handle,
+                          public_key: registration_data.public_key,
+                          counter: registration_data.counter,
+                          user: user)
+    rescue JSON::ParserError, NoMethodError, ArgumentError
+      registration.errors.add(:base, 'Your U2F device did not send a valid JSON response.')
+    rescue U2F::Error => e
+      registration.errors.add(:base, e.message)
+    end
+
+    registration
+  end
+
+  def self.authenticate(user, app_id, json_response, challenges)
+    response = U2F::SignResponse.load_from_json(json_response)
+    registration = user.u2f_registrations.find_by_key_handle(response.key_handle)
+    u2f = U2F::U2F.new(app_id)
+
+    if registration
+      u2f.authenticate!(challenges, response, Base64.decode64(registration.public_key), registration.counter)
+      registration.update(counter: response.counter)
+      true
+    end
+  rescue JSON::ParserError, NoMethodError, ArgumentError, U2F::Error
+    false
+  end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index bbc88f7e38a20614a7f58dd5508bb8ee168372e1..e0987e07e1fa885e3c250e91bc61b6867fe426a3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -27,7 +27,6 @@ class User < ActiveRecord::Base
 
   devise :two_factor_authenticatable,
          otp_secret_encryption_key: Gitlab::Application.config.secret_key_base
-  alias_attribute :two_factor_enabled, :otp_required_for_login
 
   devise :two_factor_backupable, otp_number_of_backup_codes: 10
   serialize :otp_backup_codes, JSON
@@ -51,6 +50,7 @@ class User < ActiveRecord::Base
   has_many :keys, dependent: :destroy
   has_many :emails, dependent: :destroy
   has_many :identities, dependent: :destroy, autosave: true
+  has_many :u2f_registrations, dependent: :destroy
 
   # Groups
   has_many :members, dependent: :destroy
@@ -175,8 +175,16 @@ class User < ActiveRecord::Base
   scope :active, -> { with_state(:active) }
   scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
   scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
-  scope :with_two_factor,    -> { where(two_factor_enabled: true) }
-  scope :without_two_factor, -> { where(two_factor_enabled: false) }
+
+  def self.with_two_factor
+    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
+      where("u2f.id IS NOT NULL OR otp_required_for_login = ?", true).distinct(arel_table[:id])
+  end
+
+  def self.without_two_factor
+    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
+      where("u2f.id IS NULL AND otp_required_for_login = ?", false)
+  end
 
   #
   # Class methods
@@ -323,14 +331,29 @@ class User < ActiveRecord::Base
   end
 
   def disable_two_factor!
-    update_attributes(
-      two_factor_enabled:          false,
-      encrypted_otp_secret:        nil,
-      encrypted_otp_secret_iv:     nil,
-      encrypted_otp_secret_salt:   nil,
-      otp_grace_period_started_at: nil,
-      otp_backup_codes:            nil
-    )
+    transaction do
+      update_attributes(
+        otp_required_for_login:      false,
+        encrypted_otp_secret:        nil,
+        encrypted_otp_secret_iv:     nil,
+        encrypted_otp_secret_salt:   nil,
+        otp_grace_period_started_at: nil,
+        otp_backup_codes:            nil
+      )
+      self.u2f_registrations.destroy_all
+    end
+  end
+
+  def two_factor_enabled?
+    two_factor_otp_enabled? || two_factor_u2f_enabled?
+  end
+
+  def two_factor_otp_enabled?
+    self.otp_required_for_login?
+  end
+
+  def two_factor_u2f_enabled?
+    self.u2f_registrations.exists?
   end
 
   def namespace_uniq
diff --git a/db/migrate/20160425045124_create_u2f_registrations.rb b/db/migrate/20160425045124_create_u2f_registrations.rb
new file mode 100644
index 0000000000000000000000000000000000000000..93bdd9de2eb054ca57398d6af9c6d9d51d76bba8
--- /dev/null
+++ b/db/migrate/20160425045124_create_u2f_registrations.rb
@@ -0,0 +1,13 @@
+class CreateU2fRegistrations < ActiveRecord::Migration
+  def change
+    create_table :u2f_registrations do |t|
+      t.text :certificate
+      t.string :key_handle, index: true
+      t.string :public_key
+      t.integer :counter
+      t.references :user, index: true, foreign_key: true
+
+      t.timestamps null: false
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 289021196159a9a51b8e25edc78e917420ff82a1..9b991f347a90f36198d4fb396b010610b44b233e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -12,7 +12,6 @@
 # It's strongly recommended that you check this file into your version control system.
 
 ActiveRecord::Schema.define(version: 20160530150109) do
-
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
   enable_extension "pg_trgm"
@@ -940,6 +939,19 @@ ActiveRecord::Schema.define(version: 20160530150109) do
   add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
   add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
 
+  create_table "u2f_registrations", force: :cascade do |t|
+    t.text     "certificate"
+    t.string   "key_handle"
+    t.string   "public_key"
+    t.integer  "counter"
+    t.integer  "user_id"
+    t.datetime "created_at",  null: false
+    t.datetime "updated_at",  null: false
+  end
+
+  add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree
+  add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
+
   create_table "users", force: :cascade do |t|
     t.string   "email",                       default: "",    null: false
     t.string   "encrypted_password",          default: "",    null: false
@@ -1047,4 +1059,5 @@ ActiveRecord::Schema.define(version: 20160530150109) do
   add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
   add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
 
+  add_foreign_key "u2f_registrations", "users"
 end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 1a996846e9d4f73f0dd816a8627b0493ce1ed717..66c138eb902b3cd5660e14b73d6df5ab3226fef2 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -30,7 +30,7 @@ module API
       expose :identities, using: Entities::Identity
       expose :can_create_group?, as: :can_create_group
       expose :can_create_project?, as: :can_create_project
-      expose :two_factor_enabled
+      expose :two_factor_enabled?, as: :two_factor_enabled
       expose :external
     end
 
diff --git a/spec/factories/u2f_registrations.rb b/spec/factories/u2f_registrations.rb
new file mode 100644
index 0000000000000000000000000000000000000000..df92b0795814e9a8e58c82353a2b66a29974a623
--- /dev/null
+++ b/spec/factories/u2f_registrations.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+  factory :u2f_registration do
+    certificate { FFaker::BaconIpsum.characters(728) }
+    key_handle { FFaker::BaconIpsum.characters(86) }
+    public_key { FFaker::BaconIpsum.characters(88) }
+    counter 0
+  end
+end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index a9b2148bd2ad477aa7dece6e41ca86ba417545fe..c6f7869516e207f03a22a094660e3894eacbe45e 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -15,14 +15,26 @@ FactoryGirl.define do
     end
 
     trait :two_factor do
+      two_factor_via_otp
+    end
+
+    trait :two_factor_via_otp do
       before(:create) do |user|
-        user.two_factor_enabled = true
+        user.otp_required_for_login = true
         user.otp_secret = User.generate_otp_secret(32)
         user.otp_grace_period_started_at = Time.now
         user.generate_otp_backup_codes!
       end
     end
 
+    trait :two_factor_via_u2f do
+      transient { registrations_count 5 }
+
+      after(:create) do |user, evaluator|
+        create_list(:u2f_registration, evaluator.registrations_count, user: user)
+      end
+    end
+
     factory :omniauth_user do
       transient do
         extern_uid '123456'
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 96621843b30940020676fae2e2f8c5d65163c2d7..b72ad405479822085b3abaa3d2b47d2e9da16191 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -19,7 +19,7 @@ describe "Admin::Users", feature: true  do
 
     describe 'Two-factor Authentication filters' do
       it 'counts users who have enabled 2FA' do
-        create(:user, two_factor_enabled: true)
+        create(:user, :two_factor)
 
         visit admin_users_path
 
@@ -29,7 +29,7 @@ describe "Admin::Users", feature: true  do
       end
 
       it 'filters by users who have enabled 2FA' do
-        user = create(:user, two_factor_enabled: true)
+        user = create(:user, :two_factor)
 
         visit admin_users_path
         click_link '2FA Enabled'
@@ -38,7 +38,7 @@ describe "Admin::Users", feature: true  do
       end
 
       it 'counts users who have not enabled 2FA' do
-        create(:user, two_factor_enabled: false)
+        create(:user)
 
         visit admin_users_path
 
@@ -48,7 +48,7 @@ describe "Admin::Users", feature: true  do
       end
 
       it 'filters by users who have not enabled 2FA' do
-        user = create(:user, two_factor_enabled: false)
+        user = create(:user)
 
         visit admin_users_path
         click_link '2FA Disabled'
@@ -173,7 +173,7 @@ describe "Admin::Users", feature: true  do
 
     describe 'Two-factor Authentication status' do
       it 'shows when enabled' do
-        @user.update_attribute(:two_factor_enabled, true)
+        @user.update_attribute(:otp_required_for_login, true)
 
         visit admin_user_path(@user)
 
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 528a79bf22121aaf1de70f78aed430b2336498ba..6ea8bf9bbe12b76b0209525bf3dad10458babae6 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -121,6 +121,66 @@ describe User, models: true do
     end
   end
 
+  describe "scopes" do
+    describe ".with_two_factor" do
+      it "returns users with 2fa enabled via OTP" do
+        user_with_2fa = create(:user, :two_factor_via_otp)
+        user_without_2fa = create(:user)
+        users_with_two_factor = User.with_two_factor.pluck(:id)
+
+        expect(users_with_two_factor).to include(user_with_2fa.id)
+        expect(users_with_two_factor).not_to include(user_without_2fa.id)
+      end
+
+      it "returns users with 2fa enabled via U2F" do
+        user_with_2fa = create(:user, :two_factor_via_u2f)
+        user_without_2fa = create(:user)
+        users_with_two_factor = User.with_two_factor.pluck(:id)
+
+        expect(users_with_two_factor).to include(user_with_2fa.id)
+        expect(users_with_two_factor).not_to include(user_without_2fa.id)
+      end
+
+      it "returns users with 2fa enabled via OTP and U2F" do
+        user_with_2fa = create(:user, :two_factor_via_otp, :two_factor_via_u2f)
+        user_without_2fa = create(:user)
+        users_with_two_factor = User.with_two_factor.pluck(:id)
+
+        expect(users_with_two_factor).to eq([user_with_2fa.id])
+        expect(users_with_two_factor).not_to include(user_without_2fa.id)
+      end
+    end
+
+    describe ".without_two_factor" do
+      it "excludes users with 2fa enabled via OTP" do
+        user_with_2fa = create(:user, :two_factor_via_otp)
+        user_without_2fa = create(:user)
+        users_without_two_factor = User.without_two_factor.pluck(:id)
+
+        expect(users_without_two_factor).to include(user_without_2fa.id)
+        expect(users_without_two_factor).not_to include(user_with_2fa.id)
+      end
+
+      it "excludes users with 2fa enabled via U2F" do
+        user_with_2fa = create(:user, :two_factor_via_u2f)
+        user_without_2fa = create(:user)
+        users_without_two_factor = User.without_two_factor.pluck(:id)
+
+        expect(users_without_two_factor).to include(user_without_2fa.id)
+        expect(users_without_two_factor).not_to include(user_with_2fa.id)
+      end
+
+      it "excludes users with 2fa enabled via OTP and U2F" do
+        user_with_2fa = create(:user, :two_factor_via_otp, :two_factor_via_u2f)
+        user_without_2fa = create(:user)
+        users_without_two_factor = User.without_two_factor.pluck(:id)
+
+        expect(users_without_two_factor).to include(user_without_2fa.id)
+        expect(users_without_two_factor).not_to include(user_with_2fa.id)
+      end
+    end
+  end
+
   describe "Respond to" do
     it { is_expected.to respond_to(:is_admin?) }
     it { is_expected.to respond_to(:name) }