Edited at
Vue.js #1Day 13

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

この記事は、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さんです。