この記事は、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
というファイルを作成しました。
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ファイルを作成しました。
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は次のようになります。
import Vue from 'vue'
import Cognito from './cognito'
import config from './../config'
Vue.use(Cognito, config)
export default new Cognito()
上記で作成したcognito関連の処理をプラグインとしてVueインスタンスに登録します。
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 }
})
コンポーネント
ログイン
ログイン画面は次のようになります。
<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)に遷移させています。
ユーザー登録
ユーザー登録画面は次のようになります。
<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では、ユーザーアカウント確認のフローに幾つかの種類がありますが、今回は登録メールアドレスに、確認コードを送信し、
確認コード入力画面から確認をするというフローでユーザーを登録します。
確認コードの入力画面は次のようになります。
<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>
...
ルーター
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さんです。