「usernameまたはemailでログイン」的なやつを実装するときに気を付けること

github.com

Deviseのドキュメントに具体例が載ってるのでそちらを読んでもらえると良いとは思うけど、Railsを使っていない人向けに書いておきます。

ログインの際にemailを入力するのはユーザーとしても面倒なことが多いのでusernameなどの一意なデータでもログインできるような仕様が多いと思う。

ツイッターなんかは「Phone, email or username」でログインできますね。

で本題に入ると、例えばusernameとemail、その他の情報を持つusersテーブルがあるとする

users

Field Type
id bigint(20)
username varchar(255)
email varchar(255)

あとその他カラム

で、ログインするときのクエリにはuserを見つけるために必然的に

WHERE
  username = ?
  OR
  email = ?
ORDER BY id ASC
LIMIT 1

のような条件が出てくる(ORDERはシステムによって変わってくると思うが)。

一見問題無さそうに見えるけど、例えば以下のようなデータを持つユーザーがいるとログイン時まずいことが起き得る。

id username email
1 hoge hatena@example.com
2 hatena@example.com hatena+1@example.com

id: 2のユーザーがusernameでログインしようとすると2件のレコードが該当し、LIMITでどちらかが(この場合は2が)削られ、その結果ログインできない。

対策としては

  1. usernameに「@」を入力できないようにバリデーションを掛ける。
  2. 登録時、または更新時に入力されたusernameに一致するemailがDBに存在しないか確認する。

などがある。

対策2だけでは、登録済みでない潜在ユーザーのemailアドレスをusernameに使うことはできてしまうので、対策1と併用するか、usernameには@を許可せず、かつemailには@を必須にする、などやっておくと良い