Help us understand the problem. What is going on with this article?

vue-cliで作成したSPAにシンプルにCognitoログインを組み込む

More than 1 year has passed since last update.

この記事は、Vue.js Advent Calendar 2017 13日目の記事です。

vue-cliを使って作成されたプロジェクトにCognitoを使ったユーザーログイン機能を組み込んでみたいと思います。

前提

環境

$ node -v
v8.1.3
$ vue -V
2.9.2

使用したライブラリなど

  • amazon-cognito-identity-js 1.28.0
  • aws-sdk 2.168.0

手順

以下の手順では、vue-cliでvue initした状態のプロジェクトから変更・追加のあるファイルについて実際のコードと、説明を記載していきます。記載のないものに関しては、手を加えていません。

プロジェクトのセットアップ

まずは、vue-cliでプロジェクトを作成します。
(vue-routerを使用するようにセットアップ)

$ vue init webpack
...

次に必要なパッケージをインストールします。

$ npm i aws-sdk --save
$ npm i amazon-cognito-identity-js --save

amazon-cognito-identity-jsはJavaScriptからCognitoを使い際には定番のライブラリです。

Cognito UserPoolなどのAWSに関する情報を設定ファイルに記述します。
今回は、src/config.jsというファイルを作成しました。

src/config.js
export default {
  Region: 'ap-northeast-1',
  UserPoolId: 'ap-northeast-1_XXXXXXXXX',
  ClientId: 'YYYYYYYYYYYYYYYYYYYYYYYYYY',
  IdentityPoolId: 'ap-northeast-1:XXXXXXXX-YYYY-XXXX-YYYY-XXXXXXXXXXXX'
}

Cognito User Pool、Cognito Identity Pool の作成に関しては、次の記事を参考にしてください。
Angular+Cognitoのユーザー認証付きSPAのサンプル

Cognitoサービス

cognito関連の処理をプラグインとして実装するため、新しくファイルを作成します。
今回は、src/cognito以下に次の2ファイルを作成しました。

src/cognito/cognito.js
import {
  CognitoUserPool,
  CognitoUser,
  AuthenticationDetails,
  CognitoUserAttribute
} from 'amazon-cognito-identity-js'
import { Config, CognitoIdentityCredentials } from 'aws-sdk'

export default class Cognito {
  configure (config) {
    if (config.userPool) {
      this.userPool = config.userPool
    } else {
      this.userPool = new CognitoUserPool({
        UserPoolId: config.UserPoolId,
        ClientId: config.ClientId
      })
    }
    Config.region = config.region
    Config.credentials = new CognitoIdentityCredentials({
      IdentityPoolId: config.IdentityPoolId
    })
    this.options = config
  }

  static install = (Vue, options) => {
    Object.defineProperty(Vue.prototype, '$cognito', {
      get () { return this.$root._cognito }
    })

    Vue.mixin({
      beforeCreate () {
        if (this.$options.cognito) {
          this._cognito = this.$options.cognito
          this._cognito.configure(options)
        }
      }
    })
  }

  /**
   * username, passwordでサインアップ
   * username = emailとしてUserAttributeにも登録
   */
  signUp (username, password) {
    const dataEmail = { Name: 'email', Value: username }
    const attributeList = []
    attributeList.push(new CognitoUserAttribute(dataEmail))
    return new Promise((resolve, reject) => {
      this.userPool.signUp(username, password, attributeList, null, (err, result) => {
        if (err) {
          reject(err)
        } else {
          resolve(result)
        }
      })
    })
  }

  /**
   * 確認コードからユーザーを有効化する
   */
  confirmation (username, confirmationCode) {
    const userData = { Username: username, Pool: this.userPool }
    const cognitoUser = new CognitoUser(userData)
    return new Promise((resolve, reject) => {
      cognitoUser.confirmRegistration(confirmationCode, true, (err, result) => {
        if (err) {
          reject(err)
        } else {
          resolve(result)
        }
      })
    })
  }

  /**
   * username, passwordでログイン
   */
  login (username, password) {
    const userData = { Username: username, Pool: this.userPool }
    const cognitoUser = new CognitoUser(userData)
    const authenticationData = { Username: username, Password: password }
    const authenticationDetails = new AuthenticationDetails(authenticationData)
    return new Promise((resolve, reject) => {
      cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: (result) => {
          // 実際にはクレデンシャルなどをここで取得する(今回は省略)
          resolve(result)
        },
        onFailure: (err) => {
          reject(err)
        }
      })
    })
  }

  /**
   * ログアウト
   */
  logout () {
    this.userPool.getCurrentUser().signOut()
  }

  /**
   * ログインしているかの判定
   */
  isAuthenticated () {
    const cognitoUser = this.userPool.getCurrentUser()
    return new Promise((resolve, reject) => {
      if (cognitoUser === null) { reject(cognitoUser) }
      cognitoUser.getSession((err, session) => {
        if (err) {
          reject(err)
        } else {
          if (!session.isValid()) {
            reject(session)
          } else {
            resolve(session)
          }
        }
      })
    })
  }
}

上記では、登録、確認コードからの承認、ログイン、ログアウト、セッションの確認のロジックを実装しています。
基本的には、amazon-cognito-identity-jsで用意されているメソッドをPromiseでラップしているだけです。
注意する点は、installメソッドでVueプラグインとして記述している点です。
詳細は、以下の記事が参考になりました。

index.jsは次のようになります。

src/cognito/index.js
import Vue from 'vue'
import Cognito from './cognito'
import config from './../config'

Vue.use(Cognito, config)

export default new Cognito()

上記で作成したcognito関連の処理をプラグインとしてVueインスタンスに登録します。
main.jsを次のように編集します。

src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import cognito from './cognito'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  cognito,
  template: '<App/>',
  components: { App }
})

コンポーネント

ログイン

ログイン画面は次のようになります。

src/components/Login.vue
<template>
  <div class="login">
    <h2>ログイン</h2>
    <form @submit.prevent="login">
      <div>
        ユーザー名:
        <input type="text" placeholder="username" v-model="username" required>
      </div>
      <div>
        パスワード:
        <input type="password" placeholder="password" v-model="password" required>
      </div>
      <button>ログイン</button>
    </form>
    <router-link to="/confirm">確認コード入力</router-link>
    <router-link to="/singup">ユーザー登録</router-link>
  </div>
</template>

<script>
export default {
  name: 'Login',
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    login () {
      this.$cognito.login(this.username, this.password)
        .then(result => {
          this.$router.replace('/home')
        })
        .then(err => {
          this.error = err
        })
    }
  }
}
</script>
...

cognito.jsに実装したlogin ()メソッドにフォームから取得したユーザー名(メールアドレス)とパスワードを渡します。
ログインに成功した場合、ここではホーム画面(HelloWorld)に遷移させています。

ユーザー登録

ユーザー登録画面は次のようになります。

src/components/Signup.vue
<template>
  <div class="signup">
    <h2>ユーザー登録</h2>
    <form @submit.prevent="singup">
      <div>
        メール:
        <input type="text" placeholder="メール" v-model="username" required>
      </div>
      <div>
        パスワード:
        <input type="password" placeholder="パスワード" v-model="password" required>
      </div>
      <div>
        パスワード(確認):
        <input type="password" placeholder="パスワード(確認)" v-model="passwordConfirm" required>
      </div>
      <button>登録</button>
    </form>
    <router-link to="/login">ログイン</router-link>
    <router-link to="/confirm">確認コード入力</router-link>
  </div>
</template>

<script>
export default {
  name: 'Signup',
  data () {
    return {
      username: '',
      password: '',
      passwordConfirm: ''
    }
  },
  methods: {
    singup () {
      if (this.username && (this.password === this.passwordConfirm)) {
        this.$cognito.signUp(this.username, this.password)
          .then(resutl => {
            // 登録に成功したら、確認コードの入力画面を表示
            this.$router.replace('/confirm')
          })
          .catch(err => {
            console.log(err)
          })
      }
    }
  }
}
</script>
...

ログインに成功した場合、確認コードの入力画面に遷移させています。

確認コードの入力

Cognitoでは、ユーザーアカウント確認のフローに幾つかの種類がありますが、今回は登録メールアドレスに、確認コードを送信し、
確認コード入力画面から確認をするというフローでユーザーを登録します。

確認コードの入力画面は次のようになります。

src/components/Confirm.vue
<template>
  <div class="confirm">
    <h2>確認コード入力</h2>
    <form @submit.prevent="confirm">
      <div>
        メール:
        <input type="text" placeholder="メール" v-model="username" required>
      </div>
      <div>
        パスワード:
        <input type="text" placeholder="確認コード" v-model="confirmationCode" required>
      </div>
      <button>確認</button>
    </form>
    <router-link to="/login">ログイン</router-link>
    <router-link to="/singup">ユーザー登録</router-link>
  </div>
</template>

<script>
export default {
  name: 'Confirm',
  data () {
    return {
      username: '',
      confirmationCode: ''
    }
  },
  methods: {
    confirm () {
      this.$cognito.confirmation(this.username, this.confirmationCode)
        .then(result => {
          this.$router.replace('/login')
        })
        .then(err => {
          this.error = err
        })
    }
  }
}
</script>
...

ルーター

src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import cognito from '@/cognito'
import Login from '@/components/Login'
import Signup from '@/components/Signup'
import Confirm from '@/components/Confirm'

Vue.use(Router)

const requireAuth = (to, from, next) => {
  cognito.isAuthenticated()
    .then(session => {
      next()
    })
    .catch(session => {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    })
}

const logout = (to, from, next) => {
  cognito.logout()
  next('/login')
}

export default new Router({
  mode: 'history',
  routes: [
    { path: '/',
      redirect: 'home'
    },
    {
      path: '/home',
      name: 'HelloWorld',
      component: HelloWorld,
      beforeEnter: requireAuth
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    },
    {
      path: '/singup',
      name: 'Signup',
      component: Signup
    },
    {
      path: '/confirm',
      name: 'Confirm',
      component: Confirm
    },
    { path: '/logout',
      beforeEnter: logout
    }
  ]
})

未ログインのユーザーがHelloWorld(ルート)へアクセスするのを許可しない、logoutへのアクセスがあったさいにlogout ()メソッドを実行するという処理には、vue-routerのナビゲーションガード(コンポーネント内ガード)を使っています。

以上で、最低限の機能を組み込むことができました。
実際に使用する際には、ユーザー登録時にusername,password以外の情報を登録する、ログイン後、session情報からAPIコールに必要なidTokenを取得するなどの機能を追加指定必要があるかと思います。

終わりに

このようにamazon-cognito-identity-jsを使うことで比較的簡単にCognitoによるユーザーログイン機能を実現することができます。
今回はログイン部分のみの実装だったため、Vuexは使いませんでしたが、Vuexを使うことを前提としたアプリケーションでは、AWS SDKによる非同期処理部分はStoreのAction内で行ったほうが全体の構成がシンプルになるかと思います。
ざっくりとした内容になってしまいましたが、以上で終わりたいと思います。

明日は、 @SatoTakumiさんです。

daikiojm
Blog: http://daikiojm.hatenablog.com Scrapbox: https://scrapbox.io/daikiojm
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした