AWS Amplify + Amazon Cognito ( GCP OAuth 2.0 ) + Vue.js (Vue CLI) を使ったログイン機能の実装

2020年5月17日追記

ライブラリのバージョンアップの影響のため、こちらの記事通りだとうまく行かない可能性があります。
下記の記事を参考にしてみてください。


最近 AWS Amplify を少しずつ触っているのですが、認証機能を作り込む時に私のやりたいことがまとまった情報が無かったので、その辺りを少しまとめておきたいと思います。

以下、2020年4月5日時点においての対応方法、というスタンスで。

やりたいこと

Vue.js で Google アカウントを使った認証 (GCP OAuth 2.0) 機能を実現する、というもの。

認証は Google アカウントですが、プロバイダとして Amazon Cognito を経由するような形になります。

役割名前
Web フロントエンドVue.js (Vue-Router + Vuex)
認証Amazon Cognito + GCP OAuth 2.0

Sign In 後は、 Vue-Router + Vuex を使って表示するページを制御していきます。

手順

Vue.js のプロジェクト作成

Vue CLI を使ってプロジェクトを作成します。

作成したら、 Amplify を使うためにパッケージ aws-amplifyaws-amplify-vue を追加します。

$ vue create sample-auth


Vue CLI v4.2.3
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No


Vue CLI v4.2.3
✨  Creating project in /Users/gray/projects/amplify/sample-auth.
🗃  Initializing git repository...
⚙️  Installing CLI plugins. This might take a while...

yarn install v1.16.0
info No lockfile found.
[1/4] 🔍  Resolving packages...



success Saved lockfile.
info To upgrade, run the following command:
$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
✨  Done in 17.61s.
🚀  Invoking generators...
📦  Installing additional dependencies...

yarn install v1.16.0
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 4.55s.
⚓  Running completion hooks...

📄  Generating README.md...

🎉  Successfully created project sample-auth.
👉  Get started with the following commands:

 $ cd sample-auth
 $ yarn serve

$ cd sample-auth
$ yarn add aws-amplify aws-amplify-vue
(snip)
✨  Done in 11.74s.
$

GCP で OAuth 2.0 クライアント ID の発行

Amplify で認証機能を有効にする時に “OAuth 2.0 クライアント ID” が必要になるので、事前に発行しておきます。

まずは、 GCP コンソールの “APIとサービス” > “OAuth 同意画面” を開きます。

G Suite のアカウントであれば “内部” を選べますが、一般の Google アカウント ( *@gmail.com ) の場合は “外部” しか選べません。

外部を選ぶ場合、開発中サービスのURLが漏れてしまうと、想定していないユーザーが利用する可能性もあるのでご注意ください。

同意画面では、スコープなどの設定が可能です。今回は特にいじらずにそのままにしておきます。

画面を下までスクロールして、 “保存” ボタンを押してください。

保存できたら、GCP コンソールの “APIとサービス” > “認証情報” を開きます。

ページの上の方にある “+ 認証情報を作成” をクリックし、 “OAuth クライアント ID” を選択します。

“ウェブアプリケーション” を選択すると、いくつかの入力項目が表示されます。

制限事項のURLは、二つともいったん設定無しでOKです。
(承認済みのリダイレクト URI に後ほどURLを追加します。)

作成を押すと、 “クライアントID” と “クライアントシークレット” が表示されます。
これらは次の Amplify Auth の設定で使いますのでそのままにしておいてください。
(いったん閉じても、 GCP のコンソールからも確認できます。)

Amplify 設定

Amplify プロジェクトの初期化から始めます。 Vue.js のプロジェクトのディレクトリで amplify init を実行します。
色々と質問されるので、下記を参考に進めていってください。

$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project sample-auth
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using vue
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  yarn build
? Start Command: yarn serve
Using default provider  awscloudformation

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use default
Adding backend environment dev to AWS Amplify Console app: XXXXXXXXXXXXX
⠹ Initializing project in the cloud...

(snip)

Some next steps:
"amplify status" will show you what you've added already and if it's locally configured or deployed
"amplify add <category>" will allow you to add features like user login or a backend API
"amplify push" will build all your local backend resources and provision it in the cloud
“amplify console” to open the Amplify Console and view your project status
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Pro tip:
Try "amplify add api" to create a backend API and then "amplify publish" to deploy everything

$

続けて、 Amplify Auth 使う設定を進めます。今度は amplify auth add を実行します。
こちらも色々と質問されるので、下記を参考に回答していってください。

また、ここで出てくる “Enter your Google Web Client ID for your OAuth flow:” に 先ほどの “クライアントID” を、 “Enter your Google Web Client Secret for your OAuth flow:” に “クライアントシークレット” を入力して進めます。

$ amplify auth add
Using service: Cognito, provided by: awscloudformation
 
 The current configured provider is Amazon Cognito. 
 
 Do you want to use the default authentication and security configuration? Default configuration with Social Provider (Federation)
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Email
 Do you want to configure advanced settings? Yes, I want to make some additional changes.
 Warning: you will not be able to edit these selections. 
 What attributes are required for signing up? Email, Name
 Do you want to enable any of the following capabilities? 
 What domain name prefix you want us to create for you? sampleauthXXXXXXXX-XXXXXXXX
 Enter your redirect signin URI: http://localhost:8080/authed/
? Do you want to add another redirect signin URI No
 Enter your redirect signout URI: http://localhost:8080/
? Do you want to add another redirect signout URI No
 Select the social providers you want to configure for your user pool: Google
  
 You've opted to allow users to authenticate via Google.  If you haven't already, you'll need to go to https://developers.google.com/id
entity and create an App ID. 
 
 Enter your Google Web Client ID for your OAuth flow:  "クライアントID"
 Enter your Google Web Client Secret for your OAuth flow:  "クライアントシークレット"
Successfully added resource sampleauthXXXXXXXX locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

$

設定できたので、AWSに反映させておきましょう。 amplify push を実行します。

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name      | Operation | Provider plugin   |
| -------- | ------------------ | --------- | ----------------- |
| Auth     | sampleauthXXXXXXXX | Create    | awscloudformation |
? Are you sure you want to continue? Yes
⠴ Updating resources in the cloud. This may take a few minutes...

(snip)

✔ All resources are updated in the cloud

Hosted UI Endpoint: https://sampleauthXXXXXXXX-XXXXXXXX-dev.auth.ap-northeast-1.amazoncognito.com/
Test Your Hosted UI Endpoint: https://sampleauthXXXXXXXX-XXXXXXXX-dev.auth.ap-northeast-1.amazoncognito.com/login?response_type=code&client_id=XXXXXXXXXXXXXXXXXXXXXXXXXX&redirect_uri=http://localhost:8080/authed/

$

Vue.js で認証機能を実装

認証を行なうためのバックエンド側は設定できたので、 Vue.js を使って実際に認証を行なうための実装をやりましょう。

まずは、認証を開始する /signin ページと、認証成功後に遷移させる /authed ページを実装します。

差分: https://github.com/tacck/sample-auth-vue-amplify-gcp-oauth/commit/1bb611b6e6abb305522871ac16c185d976547e45

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import Amplify, * as AmplifyModules from 'aws-amplify'
import { AmplifyPlugin } from 'aws-amplify-vue'
import awsconfig from './aws-exports'
Amplify.configure(awsconfig)

Vue.use(AmplifyPlugin, AmplifyModules)

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')
<template>
  <div>
    <button @click="signin">Google</button>
  </div>
</template>

<script>
import { Auth } from 'aws-amplify'
export default {
  methods: {
    signin: function() {
      Auth.federatedSignIn({ provider: 'Google' })
    },
  },
}
</script>

<style></style>
<template>
  <div>authed</div>
</template>

<script>
export default {}
</script>

<style></style>

Signin.vue では、 “Google” ボタンをクリックすると、 Auth.federatedSignIn({ provider: 'Google' }) が実行され、先ほど設定した認証の流れが呼び出されることになります。

それが成功したら Authed.vue に遷移して、画面上に “authed” と表示されます。

では、実際に動かしてみましょう。

yarn serve でローカルサーバを起動し、 http://localhost:8080/ をブラウザで開きます。

/signin ページへ遷移します。

“Google” ボタンをクリックします。

ここではアカウント選択になっていますが、改めてのログイン画面になる場合もあります。
ここで正しい Google アカウントを選択すると、実際に認証を行なうことになります。

すると、ここでは下記のようなエラーページとなるはずです。

これは、先ほど “OAuth クライアント ID” を作成する際に何も設定をしていなかったので、許可していないURIから利用していますよ、というエラーになっています。

そのため、今回は上記の赤線で引いている箇所のURIを “OAuth クライアント ID” の “承認済みのリダイレクト URI” に設定してあげます。

上記を追加し “保存” を押したあとに、再び /signin ページから “Google” ボタンをクリックします。
すると、 /authed へページが遷移するはずです。

これだけで、 “GCP OAuth 2.0” での認証を Vue.js で使う、という基本部分は完了です。

認証後のページの表示制御の実装

認証をしたからには、認証の前後で表示できるページの出し分けをしたいですよね。

Vuexvue-router 、を使って実装しましょう。(ついでに、 Sign Out ページも作成します。

差分: https://github.com/tacck/sample-auth-vue-amplify-gcp-oauth/commit/bd5d0f0361516e2af05bef3cfb4f120f5dca6650

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    user: null,
  },
  mutations: {
    setUser: function(state, user) {
      state.user = user
    },
  },
  actions: {},
  modules: {},
})

ここでは、ユーザ情報の出し入れの実装だけです。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import { Auth } from 'aws-amplify'
import store from '../store'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/signin',
    name: 'Signin',
    component: () =>
      import(/* webpackChunkName: "signin" */ '../views/Signin.vue'),
    meta: { isPublic: true },
  },
  {
    path: '/signout',
    name: 'Signout',
    component: () =>
      import(/* webpackChunkName: "signin" */ '../views/Signout.vue'),
  },
  {
    path: '/authed',
    name: 'Authed',
    component: () =>
      import(/* webpackChunkName: "signin" */ '../views/Authed.vue'),
    meta: { isPublic: true },
  },
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
})

router.beforeEach(async (to, from, next) => {
  const user = await Auth.currentUserInfo()
  store.commit('setUser', user)
  if (to.matched.some(record => !record.meta.isPublic) && user === null) {
    next({ path: '/signin' })
  } else {
    next()
  }
})

export default router

ここはちょっと長めですが、意味合いはそこまで難しくありません。

各ページのルーティング設定の中に meta: { isPublic: true } が設定されていれば、認証無しでも表示可能なページ、それ以外は認証済でのみ表示可能なページ、という意味にしています。

そうなるように、ページが切り替わる直前に呼ばれる router.beforeEach() を使って、認証済かどうか、表示して良いページかどうか、ということをチェックしています。

認証無しの状態で認証済ページを見ようとしたら、 /sigin へ強制的にページ遷移します。これによって、ページ表示の制御を行なうことが可能です。

また、 Auth.currentUserInfo() は、認証済ならユーザ情報を、認証無しなら null を返却するので、この値を Vuex へ格納すれば認証済かどうかという情報をサイト全体で使うことが可能になります。

<template>
  <div>
    <button @click="signout">Sign Out</button>
  </div>
</template>

<script>
import { Auth } from 'aws-amplify'

export default {
  methods: {
    signout: function() {
      Auth.signOut()
    },
  },
}
</script>

Sign Out ボタンをクリックすると、 Auth.signOut() を呼び出して認証無しの状態に遷移します。

ただここで少し ad hoc な感じになるのが /authed の中身です。

<template>
  <div>authed</div>
</template>

<script>
export default {
  mounted: function() {
    setTimeout(() => {
      this.$router.push('/')
    }, 1000)
  },
}
</script>

<style></style>

Sign In 後に Google の認証ページを経由して /authed に遷移するのですが、遷移直後は Auth.currentUserInfo()null を返却してしまいました。

しかし、今回のように setTimeout() を使って少し待った後に遷移すると、うまくユーザ情報を取得できるようになりました。

この部分は、もう少しなんとかできれば嬉しいところですね。

まとめ

AWS Amplify + Amazon Cognito で Google アカウントなどを使う場合に Vue.js はどう実装するか、という良い例が見つからなかったので、まとめてみました。

コーディング量はそれほど多くなく、 Vue.js で Google アカウントを使った認証 (GCP OAuth 2.0) 機能を、 AWS Amplify + Amazon Cognito によって実現することができることがわかったので、自分のなかで認証機能実装のハードルはかなり下がった感じです。

GitHub: https://github.com/tacck/sample-auth-vue-amplify-gcp-oauth

tacck
  • tacck
  • 北の大地の普通のソフトウェアエンジニア。
    インフラ・バックエンド・フロントエンドと、色々やります。

    初心者・若手向けのメンターも希望あればお受けします。

    勉強会運営中
    * ゆるWeb勉強会@札幌
    * スマートスピーカーで遊ぼう会@札幌

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください