devise_token_authで、Email以外でも認証を可能にする方法

deviseのデフォルトでの認証はメールアドレスのみです。

今回は、「メールアドレス」または「電話番号」、どちらでも認証を行えるよう実装したいと思います。

## Userモデルに認証のためのキーを追加する


```ruby

class User < ActiveRecord::Base
  extend Devise::Models
  devise :database_authenticatable, :registerable,
         :rememberable, :trackable, :validatable,
         authentication_keys: [:login]
  include DeviseTokenAuth::Concerns::User

  # ...

end
```

authentication_keys: [:login]にて、 認証のためのキーを:emailから、:loginに変更します。

クライアント側からは、loginキーに「電話番号」または「メールアドレス」を入れて送ります。

## Sessionsコントローラーを変更する

DeviseTokenAuth::SessionsControllerのcreateアクションをオーバーライドしてます。

```ruby

class Api::V1::Auth::SessionsController < DeviseTokenAuth::SessionsController

  def create
    field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
    @resource = nil

    if field
      q_value = get_case_insensitive_field_from_resource_params(field)

      @resource = find_resource(:email, q_value) if q_value.match?(/@/)
      @resource = find_resource(:phone_number, q_value) if q_value.match?(/\A\d{10}$|^\d{11}\z/)
    end

    if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
      valid_password = @resource.valid_password?(resource_params[:password])
      if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
        return render_create_error_bad_credentials
      end
      @token = @resource.create_token
      @resource.save

      sign_in(:user, @resource, store: false, bypass: false)

      yield @resource if block_given?

      render_create_success
    elsif @resource && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
      if @resource.respond_to?(:locked_at) && @resource.locked_at
        render_create_error_account_locked
      else
        render_create_error_not_confirmed
      end
    else
      render_create_error_bad_credentials
    end
  end

  #...

  private

  def resource_params
    params.permit(:password, :login)
  end

end
```

理想はsuperで呼び出して処理したいんですが、今回は無理でした。

なのでcreateメソッドを作り、そこに親のメソッドの処理を貼り付けて、必要な処理を追加という形にしてます。

主要なところだけ解説します。

### ストロングパラメータでの受け取り

```ruby

def resource_params
  params.permit(:password, :login)
end
```

見ての通りです。deviseのストロングパラメータをオーバーライドしましょう。

### 積集合を取る

```ruby

field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
```

resource_params.keys.map(&:to_sym)は、送られてきたパラメータのkeyをチェックし、配列にします。たとえば、値は[:password, :login]とかになります。

resource_class.authentication_keysは、先程設定した認証のためのkeyで、つまり[:login]です。

&でこの２つの積集合を取り、([:login]となります。)firstメソッドで、その中身を取っています。

つまり field => :loginです。


### fieldがあればあとはDBまで潜りに行くだけ

```ruby

    if field
      q_value = get_case_insensitive_field_from_resource_params(field)

      @resource = find_resource(:email, q_value) if q_value.match?(/@/)
      @resource = find_resource(:phone_number, q_value) if q_value.match?(/\A\d{10}$|^\d{11}\z/)
    end

```
get_case_insensitive_field_from_resource_params(field)は、resource_params[login]にスペースがあったら削除してくれるメソッドです。:emailキーの場合は、downcase!してくれますが、今回は:loginキーにしているので関係ないです。

あとはfind_resourceメソッドで入力された情報に合うユーザーがいるか探しにいきましょう。(クエリの発行)

ヒットしたら@resoucerにそのユーザーを返します。

その後の処理は、もともとの処理と変更ありませんので、解説はしません。

これで、「メールアドレス」または「電話番号」での認証について、成功です。


### <補足>


現状だと、メールアドレスが認証のためのユニークなキー(uid)となっておりNULLを受け付けません。

電話番号をuidにしたい場合、以下のような対応策があります。

```user.rb

class User < ActiveRecord::Base

  # ...

  before_validation :set_uid_from_phone_number

  def set_uid_from_phone_number
    self.uid = phone_number
  end

  # ...

end
```

```sessions_controller.rb

  #オーバーライドで、providerをデフォルト設定である"email"から変更
  def provider
    "phone_number"
  end
```

## 参考

(公式ドキュメント)
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address