🐜

OIDCを用いたID連携における "確認済みメールアドレス" の使い方と注意点

2022/01/23に公開

ritou です。

前の記事でちょっと確認済みメアドについての記載をしたあと、Twitterでちょっとやりとりしたり個別にDMで質問が来たりしたのでまとめます。

https://zenn.dev/ritou/articles/1f2619cbee320c

まとめ

  • "確認済みメアド" のユースケースはいくつかある
    • 新規登録時に自サービスで確認処理を行わずに利用
    • 未登録ユーザーがソーシャルログインしてきた時に既存ユーザーとの紐付け
  • IdPからもらった確認済みメアドをそのまま使っていい場合とそうじゃない場合がある
    • 自サービスで提供しているメールアドレスかどうかで変わる部分を許容するかどうか
    • email_provided のようなclaim があると便利かもしれない

確認済みメアドのユースケース

新規登録時の確認処理をスキップ

これはID連携、ソーシャルログインのメリットとしてずっと言われているものです。

  • IdPで確認済みなのでRPは確認せずに信用して使おう

というお話です。

よく知られているアンチパターン : メールアドレスをキーにしてユーザーを参照

一時期 Qiita の記事でポコポコ出てた実装です。
ID連携用の紐付けを持つのをサボろうとするとこんな設計に流れがちだったり、どっかのスクールで教えてるんじゃないか疑惑がありましたが、毎回コメントするのも大変なのでまとめています。

https://qiita.com/ritou/items/3bc545ec397f43e5e65a#snsアカウントから内部のユーザーを引く時のキー

  • IdP側でメールアドレスを変えた時などに動かなくなるかもよ
  • IdPで確認後にそのメアドがリサイクルされて今の所有者とは変わってるかもよ
  • IdPからもらった情報でユーザーを参照するときは "IdPの識別子 + IdPから受け取ったユーザー識別子" を利用しよう

というお話です。

新規登録時の既存ユーザーとの紐付け

例えば メールアドレス + パスワード とか、OTP via メールアドレスみたいなので認証処理をするサービスにソーシャルログインが導入された時などに

  • メールアドレス user1@example.com で登録済みのユーザーが存在
  • ソーシャルログインで 「IdP から受け取ったユーザー識別子に紐づけられているユーザーはいなかった」 で、「メールアドレスが user1@example.com だった

ってなった時に

  1. すっと既存ユーザーと紐付けする?
  2. いい感じに誘導して既存ユーザー側で認証したら紐付ける?
  3. 知らん。そのまま登録フローだ

みたいな選択肢があるのでは?っていうお話です。

1の場合、最初にIdPから受け取った識別子で参照を試みた結果該当ユーザーが存在しなかった時に限られてはいますが「よく知られているアンチパターン : メールアドレスをキーにしてユーザーを参照」の「IdPで確認後にそのメアドがリサイクルされて今の所有者とは変わってるかもよ」と同じリスクを抱えていると言えるでしょう。

そして忘れてはいけないのは 自サービス側にも「ある時点の確認後にそのメアドがリサイクルされて今の所有者とは変わってるかもよ」の可能性が存在することです。 どこかのSNSのように10年以上やってたらもう確認済みって言ったってどうなってるかわかりませんね。

2について、ヒントとして使って「あなたのメールアドレスで既に登録されているユーザーがいます。」というような誘導が考えられます。そして、この後にそのユーザーのクレデンシャル(パスワードなど)を確認できれば紐付ける感じですね。 結果的に、ログイン状態でID連携するのと同様の処理が行われていることが重要です。

あと、この話をすると、メールアドレスに紐づけられたユーザーが存在することが第3者にわかってしまうよねという指摘をもらったりしますが、単純にユーザーが申告してきたメールアドレスと一度は確認処理が行われてIdPから提供されるメールアドレスでは話が違うよというのは書いておきます。

https://qiita.com/ritou/items/00990e00151fad46df1a

話を戻しましょう。3でいいならそれでいいですが、内部でユーザーに紐づくメールアドレスがユニークになってる場合は別のメールアドレスを登録させる必要が出てくるかもしれません。

実際は1のようなサービスを何度かみたことあります が、個人的には2のように既存ユーザーの認証をした上で紐付けを行う実装をお勧めします。

ユーザーとメールアドレスの所有者が一致するとみなして良いケース

ここまでは「IdPから受け取った確認済みメールアドレスの所有者がユーザーと一致しないかもしれないケース」を考えようというお話を書いてきました。
一方で、 ユーザーとメールアドレスの所有者が一致するとみなして良いケース も存在します。

  • Google のアカウントの属性情報として渡される Gmail や GSuite のメールアドレス
  • Apple のアカウントの属性情報として渡されるメールアドレス

のように、自サービスで提供しているメールサービスのメールアドレスについては「その時点では」のユーザーとメールアドレスの所有者が一致するとみなして良いでしょう。(あと、「IdP側でメールアドレスを変えた時などに動かなくなるかもよ」の話はあるのでメアドをキーにしたID連携はアンチパターンのままです。)

例を挙げると

  • メールアドレス / パスワード を用いた認証方式を使ってきたサービスが Google とのID連携を行う
  • example@gmail.com で登録しているユーザーが存在する
  • 「Googleでログイン」を使ったら未登録のユーザー識別子とともに example@gmail.com のメールアドレスが提供された

この場合、Googleアカウントがそのメールアドレスの所有者であることは疑わなくても良さそうです。
気にしなければならないのは自サービス側のみとなるので、あとは リスク受容次第では そのまま紐付けるという選択肢もありえるのかもしれません。

email_provided claimを用いた OIDC の拡張案

こういう話は日本語じゃなくて英語で書いて然るべきところに共有しなければただの個人的な意見にすぎませんが 、上記、「自サービスで提供しているメールアドレスであること」を標準的な claim で表現しようとしたらどうすべきでしょうか? 個人的には email_provided / phone_number_provided といった claim があると良いのではないかと考えます。

  • 自サービスで "現状"(変わる可能性はあるので) 提供しているという意味合い
  • 互換性を考えると "email_verified" の値は今のままで別に良い

ぐらいな感じで提供されると上記の判定が容易にできて良さそうな気がします。

まとめ

私も「確認済みメアドを使うことで自サービスでの検証を省略できまっす!超便利」ってずっと言ってきた割にこの辺りはあまり言及してこなかったかもしれません。
いろんな前提条件がある上の細けぇ話ではありますが、例えばSaas/IDaaSなどを使って認証周りを実装されている方は自サービスの挙動がどうなっているかを確認してみてはいかがでしょうか。

ではまた!

あっ、この記事は Digital Identity技術勉強会 #iddance Advent Calendar 2021 の...(略

https://qiita.com/advent-calendar/2021/iddance

Discussion