Vue.js 2.0 でログイン (vue-router で認証が必要な URL を定義)

認証が必要な URL にアクセスした時に、まだログインしていない場合にはログインボタンを表示します。

作成にあたって以下の知識が必要になるので、順に試していきます。

- コンポーネント
- vue-router
- ルートメタフィールド、ナビゲーションガード

Playground として http://codepen.io を使用します。できあがったコードは http://codepen.io/takatama/pen/zoNeWP です。
Vue.js 2.0.7 と vue-router 2.0.2 で動作を確認しました。

なお、Vue.jsについて体系的に学ぶには「[Vue.js入門](https://amzn.to/2UkPOTj)」が超絶オススメです。ログイン、ログアウトについても丁寧に解説されています。

# コンポーネント 

ドキュメントは https://vuejs.org/v2/guide/components.html です。

User コンポーネントを作ります。プロパティとして name を持ちます。

codepen で Settings > JavaScript > Add External JavaScript で https://unpkg.com/vue/dist/vue.js と入力し、Save & Change ボタンを押します。

HTML:

```html
<div id="app">
  <About />
</div>
```

JavaScript:

```
var About = { template: '<h1>About</h1>' };

var app = new Vue({
  el: '#app',
  components: {
    'About': About
  },
});
```

# vue-router

ドキュメントは http://router.vuejs.org/ja/essentials/getting-started.html です。

Dashboard コンポーネントを追加します。/about に About コンポーネント、/dashboard に Dashboard コンポーネントをそれぞれ登録します。

Settings > JavaScript > Add External JavaScript で 
https://unpkg.com/vue-router/dist/vue-router.js を追加入力し、Save & Change ボタンを押します。

HTML:

```html
<div id="app">
  <p>
    <router-link to="/about">About</router-link>
    <router-link to="/dashboard">Dashboard</router-link>
  </p>
  <router-view></router-view>
</div>
```

JavaScript:

```js
var About = { template: '<h1>About</h1>' };
var Dashboard = { template: '<h1>Dashboard</h1>' };

var routes = [
  { path: '/about', component: About },
  { path: '/dashboard', component: Dashboard }
];

var router = new VueRouter({
  routes
});

var app = new Vue({
  el: '#app',
  router
});
```

\<router-view\>\ に選択されたコンポーネントが表示されます。

# ログインページ

ドキュメントは

- https://router.vuejs.org/ja/advanced/meta.html
- https://router.vuejs.org/ja/advanced/navigation-guards.html

です。

/about は認証不要、/dashboard は認証が必要な URL とします。認証が必要な URL にアクセスし、かつ、まだログインしていない場合にログインボタンを表示します。

HTML (変更なし) :

```html
<div id="app">
  <p>
    <router-link to="/about">About</router-link>
    <router-link to="/dashboard">Dashboard</router-link>
  </p>
  <router-view></router-view>
</div>
```

JavaScript:

認証処理をする Auth と、Login コンポーネントを追加する。/login に Login コンポーネントを登録します。

/dashboard がログインを要することを meta で定義します。ナビゲーションガード router.beforeEach を使って、ログインが必要で、かつ、未ログインの場合に /login にリダイレクトします。

/login にリダイレクトする際、もともとアクセスしようとしていた URL を query パラメーターに付与します( /login?redirect=/dashbaord )。

```js
var About = { template: '<h1>About</div>' };
var Dashboard = { template: '<h1>Dashboard</h1>' };

var Auth = {
  loggedIn: false,
  login: function() { this.loggedIn = true },
  logout: function() { this.loggedIn = false }  
};

var Login = {
  template: '<input type="submit" value="Login" v-on:click="login">',
  methods: {
    login: function() {
      Auth.login();
      router.push(this.$route.query.redirect);
    }
  }
};

var routes = [
  { path: '/about', component: About },
  { path: '/dashboard', component: Dashboard, meta: { requiresAuth: true }},
  { path: '/login', component: Login }
];

var router = new VueRouter({
  routes
});

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth) && !Auth.loggedIn) {
    next({ path: '/login', query: { redirect: to.fullPath }});
  } else {
    next();
  }
});

var app = new Vue({
  el: '#app',
  router
});

```

# ログアウト

Dashboard コンポーネントに /logout へのリンクを追加します。

JavaScript (主な差分のみ):

```js
var Dashboard = {
  template: '<div><h1>Dashboard</h1><router-link to="/logout">Logout</router-link></div>'
};
```

```js
var Logout = {
  template: '<input type="submit" value="Logout" v-on:click="logout">',
  methods: {
    logout: function() {
      Auth.logout();
      router.push('/');
    }
  }
};
```

```js
var routes = [
  { path: '/about', component: About },
  { path: '/dashboard', component: Dashboard, meta: { requiresAuth: true }},
  { path: '/login', component: Login },
  { path: '/logout', component: Logout }
];
```

# 補足 (デフォルトで認証を有効にする)

ほとんどの URL で認証を必要とするのであれば、デフォルトで認証を有効にして、認証が不要な URL にだけ isPublic: true を指定する方が安全です。認証が必要な URL に meta を定義し忘れ、誤って一般公開してしまうミスを防ぐことができます。

```javascript
// 全てのURLで認証を必要とする。isPubilc: true の場合だけ認証不要。
var routes = [
  { path: '/about', component: About, meta: { isPublic: true } },
  { path: '/dashboard', component: Dashboard}, // 認証を必要とする
  { path: '/login', component: Login, meta: { isPublic: true } }
];

var router = new VueRouter({
  routes
});

router.beforeEach((to, from, next) => {
  // isPublic でない場合(=認証が必要な場合)、かつ、ログインしていない場合
  if (to.matched.some(record => !record.meta.isPublic) && !Auth.loggedIn) {
    next({ path: '/login', query: { redirect: to.fullPath }});
  } else {
    next();
  }
});
```