nuxt.js と serverMiddleware(express) で Twitterログインを実装した話

# TL;DR

nuxt.js + serverMiddleware(express) でTwitterログインを実装した。

これは[Nuxt.jsとLaravelを使ってTwitterログイン機能を実装する](https://qiita.com/hareku/items/ea09602bf40bf0a42040)にインスパイアされて書いてます。

上記の記事では、サーバサイドをLarabelで用意していますが、
そういえばnuxtのserverMiddlewareを使えばいいんではないかって思って実装を試した。

つまりこんな感じのことを実装しています。
<img width="868" alt="Screen Shot 2020-02-20 at 13.05.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87900/bd696d26-83d6-fc81-e220-54bae909477e.png">

## twitter developer にて アプリ作成(省略)
~~アプリの説明を300文字書かなきゃいけなかったりとかこんな面倒だったっけか・・・~~

- Callback URL に http://127.0.0.1:3000/callback を設定。 (localhostは登録できない)
- Consumer KEY と Consumer Secret Key をメモ。
![tw.png](https://qiita-image-store.s3.amazonaws.com/0/87900/836bb2d5-1055-eecf-0200-4f95c52345e0.png)

## テンプレートからnuxtプロジェクトを作成

```bash
$ vue init nuxt-community/starter-template nuxt-twitter-auth
$ cd nuxt-twitter-auth
$ npm i --no-save
$ npm run dev
```

http://localhost:3000 で、まずは動作を確認します。

<img width="600" alt="Screen Shot 2018-08-22 at 12.13.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/87900/3bea8c52-1f08-7ea6-eab1-3f37969e2bf1.png">

## serverMiddleware を追加

- 必要なライブラリを追加

```bash
$ npm install --save passport passport-twitter express-session body-parser cookie-parser
$ npm install --save axios
$ npm install --save morgan # optional
```
- server/index.js を作成
- CONSUMER_KEY と CONSUMER_SECRETを設定
- 基本的にexpressなので、node経験者なら理解しやすいはず

```js:server/index.js
const express = require('express');
const passport = require('passport');
const TwitterStrategy = require('passport-twitter').Strategy;

const app = express();
app.use(require('morgan')('combined')); // optional
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(
  require('express-session')({
    secret: 'some secret',
    resave: true,
    saveUninitialized: true,
    cookie: {
      secure: 'auto',
    },
  }),
);
app.use(passport.initialize());
app.use(passport.session());

// twitter
passport.use(
  new TwitterStrategy(
    {
      consumerKey: CONSUMER_KEY,
      consumerSecret: CONSUMER_SECRET,
      callbackURL: 'http://127.0.0.1:3000/callback',
    },
    function(token, tokenSecret, profile, done) {
      profile.access_token = token;
      profile.token_secret = tokenSecret;
      return done(null, profile);
    },
  ),
);

passport.serializeUser((user, done) => {
  done(null, user);
});
passport.deserializeUser((obj, done) => {
  done(null, obj);
});

app.get('/hello', (req, res) => res.send('world'));

// twitter
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get('/auth/twitter/callback', passport.authenticate('twitter'), (req, res) => {
  res.json({ user: req.user });
});

app.get('/logout', (req, res) => {
  req.logout();
  res.redirect('/');
});

module.exports = {
  path: '/server',
  handler: app,
};

```

- nuxt.config.jsにserverMiddlewareを追加

```diff:nuxt.config.js
+ serverMiddleware: ['~/server']
```

- 起動

```bash
$ npm run dev
```

- とりあえずserverMiddlewareの動作確認

http://localhost:3000/server/hello にアクセスして world が返ってくればOK

## callback ページの作成 (twitterログイン後に返ってくるページ)

```vue:pages/callback.vue
<template>
  <section>
    <div>twitter user id: {{ user.id }}</div>
    <div>error: {{ error }}</div>
    <a href="http://127.0.0.1:3000/server/logout">logout</a>
  </section>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      user: {},
      error: null,
    };
  },
  mounted() {
    // 1
    axios.get('http://127.0.0.1:3000/server/auth/twitter/callback', {
      params: this.$route.query,
    }).then(res => {
      // 3
      this.user = res.data.user;
    }).catch(err => {
      this.error = err;
    });
  },
};
</script>
```
1. mountedでserverにtwitterから受け取ったクエリをそのまま投げてます。
2. /auth/twitter/callback で serverはprofileを返してます。↓
3. そして、vue側はtwitter userのIDを表示してます。

```js:/server/index.js
// 2
app.get('/auth/twitter/callback', passport.authenticate('twitter'), (req, res) => {
  res.json({ user: req.user });
});
```

- 最後に /pages/index.vue に `/auth/twitter` のリンクを追加すれば完成です

```vue:/pages/index.vue
<div class="links">
  <a
    href="http://127.0.0.1:3000/server/auth/twitter"
    class="button--grey">Twitter Login</a>
</div>
```

facebookとかlineもpassport使えば同じように実装できるはず :thinking: 

## DEMO

![demo.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/87900/80d685de-f72b-cd64-583a-05261b4ed998.gif)

## Source Code

https://github.com/ichiwa/nuxt-twitter-auth

おしまい。