概要
インターネット界隈で Google の Firebase が注目を集める今日このごろ、AWS Amplify を使って、ユーザー登録 / ログイン機能を雑に試してみた。
激ショボ。
構成要素
Amazon Cognito
Amazon Cognito(アイデンティティおよびデータ同期) | AWS
認証基盤を提供する AWS のサービス。
下記の 3 要素からなる。
- Cognito User Pools
- ユーザー情報の DB
- Facebook、Google アカウント等と連携可能
- Cognito ID プール (フェデレーティッドアイデンティティ)
- 認証された ID に対して、IAM ロールを使うための一時的な認証情報を付与する仕組み
- Cognito ユーザープール、Facebook、Google アカウントなどを ID として使用
- Cognito Sync (deprecated 扱い?)
- ユーザー情報を記録する DB や複数端末間で同期するための仕組みを提供
- Amazon Cognito Sync を初めて使用する場合は、AWS AppSync を使用してください
正直、上 2 つは 30 回くらい説明読んでも違いを明確に説明できる気がしない。
AWS Amplify
AWS Amplify: The foundation for your cloud-powered mobile & web apps
Cognito を JavaScript やスマホアプリからサクッと使用するための AWS 公式のライブラリ。
昔は「Amazon Cognito Identity SDK for JavaScript」という渋い名前のライブラリを公式としていたが、 Firebase に影響されたのか、「Amplify」とイケてる感じに改めたらしい。
Vue.js
所謂フロントエンド JS のライブラリ。UI 周りを作るためのもの。
最近は名前を目にしない日がないくらい流行ってる。
それはそうと、React って難しいよね。
コード
作り方
- 詳細は、前述のコードなどを参照していただけたら幸いです
- 筆者は Vue.js も最近のフロントエンド周りも全部素人です
前提
- Node.js インストール済み
- Vue CLI 3 を使用
- S3 でホスティングする都合上、SPA (single-page application) でなく、MPA (multi-page application、要は普通の Web ページのような感じ) として作成
Cognito ユーザープール作成
認証用のユーザープール。
とりあえず、デフォルト設定で作成。
ここの属性周りの設定は作成時しか設定できない (後から変更できない) ので、 実運用時は慎重に設定を検討しよう。
作成後にアプリクライアントを作成して、このユーザープールを使うアプリの名前や設定を指定。
アプリクライアント ID は後ほど使うのでメモっておく。
Web アプリから使う場合は、クライアントシークレットを生成
のチェックを外す。
Cognito ID プール作成
こっちは認可用の ID プール。
Cognito
タブで先程作成したユーザープールの ID とアプリクライアントの ID を指定。
こちらで指定する IAM ロールが認証済みユーザーに認可される。
Amplify で Cognito ユーザー登録・ログインするまで
Amplify インストール。
$ npm install --save aws-amplify
ドキュメントを参考に、ユーザー登録 / ログインまわりの処理を実装。
といっても、Amplify が全部よしなにやってくれるのでなんもやることないっす。
src/utils/AwsUtil.js
import Amplify, { Auth, API, } from 'aws-amplify'; Amplify.Logger.LOG_LEVEL = 'DEBUG'; Amplify.configure({ Auth: { region: process.env.VUE_APP_AWS_REGION, identityPoolId: process.env.VUE_APP_AWS_COGNITO_ID_POOL_ID, userPoolId: process.env.VUE_APP_AWS_COGNITO_USERPOOL_ID, userPoolWebClientId: process.env.VUE_APP_AWS_COGNITO_USERPOOL_CLIENT_ID, }, API: { endpoints: [{ name: process.env.VUE_APP_API_NAME, endpoint: process.env.VUE_APP_API_ENDPOINT, region: process.env.VUE_APP_AWS_REGION, }] }, }); export function signUp(username, password) { return Auth.signUp({ username, password, }); } export function signIn(username, password) { return Auth.signIn(username, password); } export function signOut() { return Auth.signOut(); }
あとは、コイツらをユーザー登録 / ログイン画面でボタン押下時に実行すれば良い。
Vue CLI インストール
$ npm install -g @vue/cli
Vue CLI でスキャフォールド
参考: Creating a Project | Vue CLI 3
$ vue create hello-world Vue CLI v3.0.1 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, Router, Linter ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a linter / formatter config: Airbnb ? Pick additional lint features: Lint on save ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json ? Save this as a preset for future projects? No
MPA としてガワを実装
vue.config.js
の pages
がミソ。
この設定によって、各ページが個別の html として outputDir
へ吐き出されるようになる。
vue.config.js
module.exports = { baseUrl: '/public', outputDir: 'dist', assetsDir: 'assets', runtimeCompiler: false, productionSourceMap: true, parallel: true, css: { modules: false, extract: true, sourceMap: false, }, lintOnSave: true, pages: { signin: { entry: 'src/main.js', template: 'public/pages/index.html', filename: 'pages/signin.html', title: 'Sign in', }, signup: { entry: 'src/main.js', template: 'public/pages/index.html', filename: 'pages/signup.html', title: 'Sign up', }, signout: { entry: 'src/main.js', template: 'public/pages/index.html', filename: 'pages/signout.html', title: 'Sign out', }, sorry: { entry: 'src/main.js', template: 'public/pages/index.html', filename: 'pages/sorry.html', title: 'Sorry', }, }, };
後は雑に適宜作成。
src/router.js
import Vue from 'vue'; import Router from 'vue-router'; import SignIn from './views/SignIn.vue'; import SignUp from './views/SignUp.vue'; import SignOut from './views/SignOut.vue'; import Sorry from './views/Sorry.vue'; Vue.use(Router); export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/pages/signin.html', name: 'signin', props: true, component: SignIn, }, { path: '/pages/signup.html', name: 'signup', props: true, component: SignUp, }, { path: '/pages/signout.html', name: 'signout', component: SignOut, }, { path: '*', name: 'sorry', component: Sorry, } ], });
src/views/SignUp.vue
<template> <div class="singup"> <h1>Sign up</h1> <form @submit.stop.prevent="signUp"> <p v-show="msg">{{ msg }}</p> <p v-show="errMsg">{{ errMsg }}</p> <input type="email" placeholder="Email" v-model="email" required> <input type="password" placeholder="Password" v-model="password" required> <input type="submit" value="Submit"> </form> <p>Do you have an account? <router-link :to="{ name: 'signin'}">Sign in now!!</router-link> </p> </div> </template> <script> import * as UserUtil from '@/utils/UserUtil'; export default { name: 'SignUp', props: ['flashMsg', 'flashErrMsg'], data() { return { email: '', password: '', errMsg: this.flashErrMsg, msg: this.flashMsg, }; }, methods: { async signUp() { try { await UserUtil.signUp(this.email, this.password); this.$router.push({ name: 'signin', params: {flashMsg: '確認メールのリンクをクリックしてからサインインしてください。' }}); } catch(e) { this.errMsg = e.message; } }, }, }; </script>
src/views/SignIn.vue
<template> <div class="singin"> <h1>Sign in</h1> <form @submit.stop.prevent="signIn" method="post"> <p v-show="msg">{{ msg }}</p> <p v-show="errMsg">{{ errMsg }}</p> <input type="email" placeholder="Email" v-model="email" required> <input type="password" placeholder="Password" v-model="password" required> <input type="submit" value="Submit"> </form> <p>Don't you have an account? <router-link :to="{ name: 'signup'}">Sign up now!!</router-link> </p> </div> </template> <script> import * as UserUtil from '@/utils/UserUtil'; import * as AwsUtil from '@/utils/AwsUtil'; export default { name: 'SignIn', props: ['flashMsg', 'flashErrMsg'], data() { return { email: '', password: '', errMsg: this.flashErrMsg, msg: this.flashMsg, }; }, methods: { async signIn() { try { await UserUtil.signIn(this.email, this.password); } catch(e) { this.errMsg = e.message; } }, }, }; </script>
動作確認
$ npm run serve > App running at: > - Local: http://localhost:8080/public/ > - Network: http://192.168.xxx.xxx:8080/public/ > > Note that the development build is not optimized. > To create a production build, run npm run build.
http://localhost:8080/public/pages/signin.html
へアクセスすると冒頭のような画面が表示されるはず!!
ブラウザの開発者ツールを開いて、LocalStorage になんか保存されていれば OK。
S3 へデプロイ
ビルド。
$ npm run build
下記のようなファイルが生成される。
gulp で S3 へデプロイするやつを設定してから、下記を実行してデプロイ。
$ npm run sync
成し遂げたぜ。