前回、Rails と Vue.js を用いた SPA でログイン の仕組みを作ってみるを書きました。
セッションを cookie に保存しましたが、今回は local storage に保存してみたいと思います。
ただし、local storage でセキュアな情報を管理する危険性についても語られています。
たとえば、以下のような記事があります。
というわけで、「やれることは確認するけど、実際はやらないかもな」という記事です。
目次
参考
実装
準備
前回の Cookie でセッション管理を下実装を改修する形で進めます。
Rails と Vue.js を用いた SPA でログイン の仕組みを作ってみる
改修(バックエンド)
認証
ID とパスワードの設定をで認証のチェックをするauth_controller.rb
は以下の通りです。
app/controllers/api/auth_controller.rb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Api::AuthController < Api::Base def verification if u= User.verification(auth_params) render json: {state:"success",msg:"Login Success",token:u.set_session_key} , status: 200 else render json: {state:"failure",msg:"Error"} , status: 403 end end
private def auth_params params.require(:user).permit(:name, :password) end
end
|
認証できたら、json のレスポンスにトークンを含めるようにします。
ユーザー情報の提供
発行したトークンをBearer
トークンとしてクライアントが送って来るように後で実装します。
Bearer
トークンを検証して、ユーザーに情報を返すようにusers_controller.rb
を改修します。
app/controllers/api/users_controller.rb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Api::UsersController < Api::Base include ActionController::HttpAuthentication::Token::ControllerMethods
before_action :authenticate
def profile if @user render json: {state:"success",msg:"User profile",profile: {name:@user.name } } , status: 200 else render json: {state:"failure",msg:"Error"} , status: 403 end end
def log_out if @user.remove_session_key render json: {state:"success",msg:"Log Outed" } , status: 200 else render json: {state:"failure",msg:"Error"} , status: 403 end end
private def authenticate authenticate_or_request_with_http_token do |token, options| @user = User.find_by(session_key: token) @user.present? end end
end
|
改修(フロントエンド)
ログイン
ログインページのfront/src/views/LogIn.vue
では、トークンを受け取って localstrage に保存します。
front/src/views/LogIn.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <template> <div class="log_in"> log in <div> <div>{{ this.message }}</div> <input type="text" v-model="name" placeholder="NAME" /> <br /> <input type="password" v-model="password" placeholder="PASSWORD" /> <br /> <button v-on:click="login()">LOGIN</button> </div> </div> </template>
<script> const axios = require("axios"); const qs = require("qs");
export default { name: "logIn", components: {}, data: function () { return { name: "", password: "", message: "", }; }, methods: { login: async function () { const self = this; const result = await axios .post("/api/auth/verification", { user: { name: this.name, password: this.password, }, paramsSerializer: function (params) { return qs.stringify(params, { arrayFormat: "brackets" }); }, }) .catch(function () { self.message = "入力エラー"; });
if (result.data.state == "success") { // 受け取ったトークンを保存する localStorage.setItem("my_site_token", result.data.token); this.$router.push("/"); } }, }, }; </script>
|
情報取得
Home.vue
では、保存したトークンを使って API に問い合わせをします。
front/src/views/Home.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| <template> <div class="home"> {{ name }} <br /> <button v-on:click="logout()">Log Out</button> </div> </template>
<script> const axios = require("axios");
export default { name: "Home", components: {}, data: function () { return { name: "", }; }, methods: { getProfile: async function () { const self = this;
const token = localStorage.getItem("my_site_token"); if (token === null) { this.$router.push("/log_in"); return; }
// headersを使用してBearerトークンを埋め込む const result = await axios .get("/api/user/profile", { headers: { Authorization: `Bearer ${token}`, }, }) .catch(function () { self.$router.push("/log_in"); return; });
if (result === undefined) { return; }
if (result.data.state != "success") { this.$router.push("/log_in"); return; }
this.name = result.data.profile.name; }, logout: async function () { const self = this; const result = await axios .post("/api/user/log_out", { withCredentials: true, }) .catch(function () { self.$router.push("/log_in"); });
if (result === undefined) { return; }
if (result.data.state != "success") { self.$router.push("/log_in"); } }, }, mounted: async function () { await this.getProfile(); }, }; </script>
|
動作確認
ここまでできたら、bundel exec rails s
で、Rails を起動。
front
ディレクトリで npm run serve
を実行し、webpack-dev-server
を起動します。
webpack-dev-server
は、標準だと 8080 番ポートで起動しているはずなので、ブラウザでlocalhost:8080
にアクセスします。
すると/log_in
にリダイレクトされるので、コンソールから設定した name と password を入力してログインします。
/
に遷移して、現在ログインしているユーザーの name が表示されています。
動作が確認できました。
今回は localstrage を使用しているので、Cookie の削除は関係ありません。
開発ツールのコンソールでlocalStorage.removeItem("my_site_token");
を実行し、トークンを削除します。
リロードすると、/log_in
にリダイレクトされます。
再度ログインし、localStorage.getItem("my_site_token");
を実行すると値が返ってきます。
ドメインの異なる別のページを開きlocalStorage.getItem("my_site_token");
を実行します。
すると,null
が返ってきます。
別のドメインでは、アクセスできないようです。
しかし、この点に関して完ぺきではないという危険性が示されている記事も多くあります。
今回は、localstrage
を使ってセッション情報の管理を行えるように SPA アプリを改修してみました。
調べれば調べるほど localstrage をセキュアな情報の管理に使用するな!という記事が多かったです。
これが現在の決定版!というところに落とし込めるように、もう少し調べを進めたいところです。
ではでは。