Webサービスで本当にパスワードっているの??という意識高いスタンスから、
むかしQiitaにRailsでパスワードなしログインの実装について記事を書きました。
完全に使うあてもなく趣味で作った機能だったけど、
本体のSorceryにプルリク送ったらかなり歓迎されて、
あとはテストさえ書けば取り込んでもらえそうな状態です。
(じゃあ書けよっていう。。)
Add Magic Login submodule by athix · Pull Request #8 · Sorcery/sorcery · GitHub
しかし最近はFirebaseがマイブームなので、Firebase版のパスワードなしログインシステム「Magic Login」を実装してみました。
Firebaseには匿名認証の仕組みもあるので、ユーザー登録なしでも使えて、本登録するときは「MagicLogin」という組み合わせにすると、かなりいい感じになりましたよと。
環境
- Firebase: 3.5.0
- Riot.js: 3.0.0
- SemanticUI: 2.2.4
Firebase x Riot.jsのセットアップ
この2つを使ったシステムのセットアップについては、
むかしQiitaに記事書きましたのでこちらを参考にしてください。
以下この記事の内容をベースに追加修正していきます。
実装方針
- サイトに訪問したら自動で匿名ユーザーとしてログインさせる
- メアドを入力したら、そのアドレスにトークン付きのワンタイムログインリンクを送る
- リンクを踏んで来たらログインさせる
- 匿名ユーザーで作成してたデータを本ユーザーアカウントに紐づける(今回は省略)
こんな感じ。
今回はユーザーが新規登録かログインかを意識せずに使えるように設計してみました。
メアド入れたときに、既登録ならそのままログインリンクを送り、未登録なら新規登録した上でログインリンクを送る、という感じにしています。
新規登録とログインの間違えってけっこう多いし、そんなんシステムで判断してくれよ、って個人的には思うのですよね。。
ログイン画面
完成イメージ
こんな感じの見た目にしていきます。
Slackのメールだけログイン画面にあからさまに影響を受けた、メールと魔法の杖のアイコンがすてきです。
なんとこれ、画像一切つかわずやってるってなかなかすごくないですか。
SemanticUIのアイコン合成機能です。Semanticすごい!
実装
auth.tagをこんな感じで実装。
<auth> <div class="ui padded basic segment"> <br><br> <div class="ui three column center aligned stackable grid"> <div class="ui column"> <div class="ui center aligned basic segment"> <i class="icons"> <i class="purple inverted mail circular huge icon"></i> <i class="horizontally flipped wizard huge icon"></i> </i> <h1 class="ui header"> Magic Login <div class="sub header"> Get a magic linked email for super easy sign-in;) </div> </h1> <div class="ui action fluid input"> <input type="text" ref="email" placeholder="Type your email here."> <button class="ui pink right labeled icon button" onclick={ magicAuth }> <i class="send icon"></i> Send </button> </div> <div if={ message } class="ui visible left aligned basic segment { message.type } message">{ message.text }</div> </div> </div> </div> <br><br> </div> <script> var that = this firebase.auth().onAuthStateChanged(function(user) { that.user = user }) magicAuth() { that.errorMessage = '' var newPassword = Math.random().toString(36).slice(-12) firebase.auth().createUserWithEmailAndPassword(that.refs.email.value, newPassword).then(function(){ //新規ユーザーの場合 firebase.auth().sendPasswordResetEmail(that.refs.email.value) firebase.auth().signOut() that.message = { type: 'success', text: 'ログイン用のメールを送信しました。メール内のリンクをクリックしてログインしてください。' } }).catch(function(error) { //アドレスが既に登録済みの場合 if(error.code == 'auth/email-already-in-use') { firebase.auth().sendPasswordResetEmail(that.refs.email.value) that.message = { type: 'success', text: 'ログイン用のメールを送信しました。メール内のリンクをクリックしてログインしてください。' } //validationエラーなど }else { that.message = { type: 'error', text: error.message } } }).then(function(){ that.update() }) } </script> </auth>
キモはfirebase.auth().sendPasswordResetEmail(that.refs.email.value)
の部分で、要するにパスワードリセットメールを送ってるのですね。
これを踏んだあとの処理を魔改造することで、マジックログインを超簡単に実装しています。
次はそっちを見てみましょう。
リンク踏んだあとの処理
riot.jsのroutingで、ログインメールのリンク踏んだときの処理を受け付けています。
route('/auth..', function(){ var q = route.query() if(q.mode == 'resetPassword') { firebase.auth().verifyPasswordResetCode(q.oobCode).then(function(email) { var newPassword = Math.random().toString(36).slice(-12) firebase.auth().confirmPasswordReset(q.oobCode, newPassword).then(function(){ firebase.auth().signInWithEmailAndPassword(email, newPassword) alert('ログインしました') }) }).catch(function(error){ alert('ログインに失敗しました..') }) } })
auth?mode=resetPassword&oobCode=xxxxxx
という感じのURLを踏んでくるイメージです。
(パラメータはFirebaseが自動付与)
やってることは、
verifyPasswordResetCode(q.oobCode)
でトークンの検証- 正しいトークンならランダムに生成した文字列を新パスワードに設定
- 新パスワードでログイン
というだけです。
キモは新パスワードをユーザーが意識することはないし、システム側でも管理しないというところ。超セキュア!
次ログインするときはまたリセットするだけですからね。
Firebaseの設定
Firebase側では、ログインメール(=パスワードリセットメール)のリンクURLを設定しておきます。
Consoleで、
Authentication > メールテンプレート > パスワードの再設定
から設定を編集し、「アクションURLをカスタマイズ」というメニューで好きなURLを設定しておきます。
https://hogehoge.com/auth
こんな感じで設定しておけば、パラメータ部分はFirebaseが自動で付与してくれます。
まとめ
というわけで、Firebaseの標準機能に乗っかることで超お手軽にMagicLoginが実装できました。
この記事では触れなかったけど、アクセス時に全員匿名ユーザーとして裏でログインさせておいて、 データを永続化したいときはこの仕組みで簡単ログイン、という組み合わせにするとかなりいい感じです。
このサービスで実践投入してますので、よければどんな感じか見てみてください。
年表たのしいでよ。