MEAN(mean.io)ではpassport-twitterが動作しない話

まとめ

MEAN(mean.io)ではpassport-twitterが(そのままでは)動作しないので、強引に回避するか諦めるかする必要がある。

何があったのか

MEAN(mean.io) を触っていたのだけれど、passport-twitterで認証すると下記のようなエラーが発生していた。

Error: Failed to find request token in session

よくあるミスで、localhostにアクセスしているのに、Twitterのcallback URLは127.0.0.1にしていてCookieが共有されていないという原因で発生しがちなエラーのようだけど、当然それも踏んだ上でまだ発生し続ける。

コンソールにエラー出力させてみると、別のエラーが原因でセッションからトークンが削除されているみたいだった。曰く、emailがないとのこと。

ValidatorError: Path `email` is required.

問題の箇所は、ログイン認証後にユーザ情報を格納する部分で、Mongooseのスキーマ上はemailフィールドは必須になっているけれど、Twitterの認証後処理にはemailを保存する処理が書かれていない

これが実装ミスかというとそうでもなくて、Twitterのユーザ情報取得にはGET users/showを参照しているけれど、そもそもTwitter APIではメールアドレス情報は返ってこないので保存する項目に記載がないのはある意味で正しいとも言えるのだった。

つまり、MEAN(mea.io)のユーザ管理上はメールアドレスを必須としているけれど、メールアドレスを取得できないTwitterでは、認証連携はうまく動作しないということらしい。しれっと対応している風に実装されているけれど、確実に動作しないという罠であって5時間くらい潰れた。猫のうんこ踏め。

傾向と対策

Twitterでメールアドレスを取得する方法はないのかというと、残念ながら存在しない。

メールアドレスや電話番号の扱いについては、少なくとも直接公開されることはない旨がヘルプに記載されているAPI経由での取得については数年前から要望には上がっていて、つい先日、許可されたアプリにのみaccount/verify_credentialsのオプションで取得が可能になったと公式からの告知があった。なんとなくヘルプの記載内容とは矛盾している気もするけれど。

いずれにしても、他のソーシャル連携と比較してスコープが雑なTwitterでは、現状の仕様のままメールアドレスが取得できるようにはならないだろうし、なったら危険な感じはする。

アプリケーションの要件にもよるけれど、MEAN(mean.io)のユーザ情報で何故メールアドレスが必要かといえば、パスワード紛失時の再発行などの用途以外で必須とする理由はあまりない(内部的にはユーザをユニークに特定するために使っているけれど、これはメールアドレス変更などを考えると若干筋が悪い気もする)。他サービス連携においては、パスワード紛失などは書くサービスに任せればよいので、単純にこの必須条件を外してしまってもよいとも思ったけれど、どこで参照しているかチェックするのも面倒だったので、ダミーの値を捏造して格納するようなことを考えた。

根本的には、アプリ自体をメールアドレス必須でなくして、メールアドレス取得可能なほかサービスでも不要な情報を保存しないようにするべきではあるのだろうなあ。

とりあえずの対応

実はこの問題、以前にIssueにも上がっていたようなのだけれど、議論が中途半端なまま半年くらいが経過して閉じられてしまっている。

回避派の人のだいたい考えることは同じで、必須条件を外すかダミーの値を生成するような感じである。ドメイン部分以外はほぼ同様で、ダミーの値をこういう感じに生成するようにして回避した。解決した気分じゃない。

packages/users/passport.js

  user = new User({
    name: profile.displayName,
    username: profile.username,
    email: profile.username + '_twitter@example.com',    // この行を追加
    provider: 'twitter',
    twitter: profile._json,
    roles: ['authenticated']
  });