2019.11.7
Vueでカクテルデータベースをリファインする話#17【管理画面をつくろう#1】
とりあえず雰囲気は作り終えたので、次は管理画面です。
管理画面とはいっても、今更新しくどうこう、みたいなのはないのですが
認証だけは学習しなくてはいけないので、どう実装するのか調べます。
雰囲気としては
このページなんかが参考になるかな。
動きとしては、axiosで認証情報を送って、クリアしたらリダイレクトって感じですかね。
認証情報へのアクセスはPHPを経由するので、サーバー側で認証できれば問題ない感じ・・・・・・?認証のセッションって自動で送信されてんのかな。
このへんフレームワーク任せだから知識浅いんだよな
まぁいいや、動けば正義なんだよ←
さて、認証画面ですね。デフォルトだと認証フォームのページまでご丁寧に用意してくれてるんですが、今回の場合自分で作らなきゃいけないです。まぁ僕しか使わないはずなので雑にフォームタグでごにょればいっか・・・・・・
でも僕以外にも管理者作る予定ではあるんだよな・・・・・・
まぁそのへんは後で考慮しよ(笑)
で、実装なんですが、Passportと迷った結果JWTでやろうと思います。
理由は、将来的にはソーシャルログインを学習しなくてはならないんですが、
それに関するサンプルコードがあったのでまずはお手軽なJWTから手を付けることにしました。
あと、こっちの方がお手軽に実装できるのでサンプルコードもたくさん転がってます。
セキュリティ的に不安な部分もあるようなんですけど、普通に実装してたら特に問題は起こらないでしょさすがに・・・・・・。実装してから勉強しよ、、、
Passportの方がOAuthを用いてるということもあって安全に近そうなんですけど、いかんせん複雑でよーわからん......
とりあえず
ここのコードがそれっぽく動いたので、それを元に考えてみます。
まずJWTの構造としては、認証情報をサーバーに送り(IDとパスワード、など)、認証にクリアすればユーザー情報のオブジェクトを返し、それをlocalStorageに格納することで、あとはそれをヘッダにくっつけておけば認証済としてアクセスできる、みたいな仕組みですね。
欠点としてはトークンが盗まれた際に、オンタイムでログアウトを強制できないことなんですが、localStorageの値が盗まれるのはXSS脆弱性(任意のjsが実行される)が入ってる場合、つまりセッションハイジャックの場合だと思うので、XSS脆弱性を発生させなければとりあえず大丈夫ということにしておきます。カクテルDBではXSS脆弱性が発生することはないので大丈夫だと思います。
ユーザーの入力値をhtmlとして出力する場所がないのでXSSは発生しないものだと僕は思ってるんですけど大丈夫なんですかね?wwwww
まぁこんな感じです。(見づらいけど)
JWTを導入する他のメリットとしては、今回の例ではあんま関係ないですが
DBサーバーへのアクセス集中緩和とかも挙げられますね。
確かにトークンを使って認証してるわけじゃないですからね・・・・・・。そのトークンの実体をどこで確認してるのか全くわかんないんですけど。ハッシュ値と秘密鍵でなにを見てるんですかね・・・・・・?ソルトとかくっつけたらハッシュが変わるから、その値を戻したらそのソルトを抜いて・・・・・・みたいなことやってんのかな。知らんけど。
とりあえず、これで既に登録されたユーザーに関してはログインできそうなのですが、ログイン状態の場合常にヘッダーにBearerを仕込む部分がよくわからんですね。探します。
要するに、一定ルート下の場合、localStorageからトークンを読み出して自動でヘッダつけて送信する、っていうのができればいいので
ルート単位ガードが使いやすそうです。
つまり、"/admin"以下にアクセスする時は常にこれを実行する...みたいなのが容易にできそうです。うまくいけばnext()を実行し、うまく行かなかったらリダイレクト。
で、実際の関数の中身は...バシっとハマる実装例が見当たらなかったので
これを参考に試してみます。
認証部分はlaravel側で上手く行ってるっぽいので、login.vue内でそのトークンをlocalStorageに保持し、それをrouter内で見ることでアクセス制御する、って方針でいいかなって思います。jwtなのでログイン状態はサーバー側で保持してないのでね。
それでもって、サーバー側から認証データを取り出したい際は、bearerをセットして送信することでミドルウェアを通過できる、そんな構成になってそうな感じがしますね。axios通信時のbearerセットは人力でやらざるを得なさそうですね。ルート統御をrouter側でやっちゃってるわけなんで・・・・・・。ここがちょっと頭悪い実装になりそう、、、
まぁエレガントな方法があるとすれば、認証済みのaxios通信部分をこれまたモジュール化しちゃうとかできればいいですね。そしたらbearer云々を書くのは一箇所で済みそうです。将来的にはそういう方針でやりたいな。おけおけ。
うーん・・・・・・なかなか正解がつかめないな・・・・・・
とりあえず
このページを読んでみたんですが・・・・・・
例えばルーティング部分は、ごちゃごちゃ書いてますが、要するとストアのis_loginみたいなのを参照するだけで良さそうですね。ストアはVuexでグローバルに持たせれば良いかな。
で、フラグがtrueだったらnext、falseだったらリダイレクト、そんな感じのものを書けばいいですね。
ログインページに関してですが、api/loginに認証情報を渡せば、返ってくるのがトークンってのがわかってるわけです。
あ、ここ!!!ビビっときましたね。なーーーーるほど、ここでaxiosに
デフォルトでヘッダに認証情報をつけるっていうのをやってるっぽいと予想。
axiosはグローバル変数なはずなので、一旦ぶちこめば(SPA外の)ページ遷移するまでは保持されるってことですね。
そのおかげでこういう動きができるわけです。コードを見ると、普通にaxiosでgetしてるだけにしか見えないんですが、ちゃんとヘッダが送信されている(はず)なので・・・・・・
ここのミドルウェアが働いてくれるんだと思います。わけわかんないヘッダがあればガードされます。auth:apiでjwtを指定してるので、そのへんのトークンの判定はライブラリ側が勝手にやってくれます。
ちなむと、これがとりあえずcurlは上手く行ったコードなんですが、APIのコントローラーですね。
auth()の出どころが不明で、コードジャンプしてもよくわからんhelperに飛んでっちゃったんですが、Authファサードでも使ってるんだろうか。ただ自分の認証情報が取れてるのは確かなので、DBのユーザー情報にアクセスする時はこれで良さそう。
ちなみにUserモデルでhiddenにしておけばパスワードも漏れないです。実際漏れたところで秘密鍵がないと解読できないと思うんですけどね。
ログアウトですね。これは単純に必要な情報を消去してるだけですね。
サーバー側にもログアウトを通知しておきます。
これだけわかれば
僕の読み方が合ってたら実装できそうなんですが、ひとつ気になるのが
Remember Meはどうやって実装するのかなってとこですね。
ただ、jwtはブラウザ側で認証情報を持ってるだけなので、有効期限を無限にすれば、ログアウトされずに残り続ける、そういった感じなんだろうか。
トークンという鍵があれば、apiはいつでもどうぞ〜状態だし、その鍵を持ってなかったら、認証情報を送って鍵を受け取ってね、loginページで受け付けてますよ〜と。そういう認識でいればいいので、Remember meとか考慮する必要がないというか、仮にそういうoptionalな機能をつけるとしたら、トークンの有効期限をサーバー側で設定してあげたらいい話ですね。解決。
応用的な話として
リフレッシュトークンとかいう話はあるんですが・・・・・・トークンの有効期限を永遠にするのはいいんですけど、リフレッシュトークンとかの話はどこに行ったのって感じですね。一応ライブラリのissueで検索して調べてるんですが・・・・・・
さすがにリフレッシュなしはこわいんだよな〜・・・・・・
例えば
こんなエントリがありました。リフレッシュトークンを返せるみたいですね。
他にも
こことかだともっと詳しく書いてますね。
あ、ってか
これじゃね???全部英語だけど(笑)
そう〜〜〜口で説明するよりコード見せて欲しかったんだよ〜〜〜!!!なるほどね、例外が投げられるんですね!?!?!?!?
なるほどなぁ。