AWS AmplifyでFacebook連携を行う

AWS AmplifyでFacebookログインを実装した時にかなりつまりました。
同じような方がいると思いますので、メモとして残しておきます。

# 環境
- Amplify 4.13.4
- Yarn 1.22.4
- Node.js 10.15.1
- Vue.js 2.6.11

# Facebookの設定1
- [Facebook for Developers](https://developers.facebook.com/?locale=ja_JP)へ登録します
- Create Appでアプリを登録します.アプリの利用目的は今回はその他を選択しました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/630e08b5-23be-4e31-fd47-ef310fef06f4.png)

- アプリ名は適当に入力してアプリIDを作成します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/d8494ab1-2eb3-e31a-9624-c09b282cc1a4.png)

- 上部にあるアプリIDと左側の設定 > ベーシックからシークレットIDをコピーしておきます(後ほど使います)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/a38ea109-acf8-4080-cd18-e734dea22402.png)


# Amplifyの設定
## Authの設定
- Facebookログインの機能を追加します
- Facebook側でログインをした後、リダイレクトされる先は一旦ログイン画面としています.ご自身の環境に合わせて修正ください
  - LocalとProductionそれぞれ2つ追加しています.Localのポートなどはご自身の環境に合わせて修正ください
- FacebookのアプリIDとシークレットキーを聞かれたら、控えておいた値を入力するようにします

```
$ amplify add auth
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? No, I am done.
 What domain name prefix you want us to create for you? test1ec8a379-1ec8a379
 Enter your redirect signin URI: https://localhost:8081/login/ # Local
? Do you want to add another redirect signin URI Yes
 Enter your redirect signin URI: https://www.test.com/login/ # Production
? Do you want to add another redirect signin URI No
 Enter your redirect signout URI: https://localhost:8081/login/
? Do you want to add another redirect signout URI Yes
 Enter your redirect signout URI: https://www.test.com/login/
? Do you want to add another redirect signout URI No
 Select the social providers you want to configure for your user pool: Facebook
  
 You've opted to allow users to authenticate via Facebook.  If you haven't already, you'll need to go to https://developers.facebook.com and create an App ID. 
 
 Enter your Facebook App ID for your OAuth flow:  [FacebookのApp ID]
 Enter your Facebook App Secret for your OAuth flow:  [FacebookのSecret ID]
Successfully added resource test1ec8a379 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

```

- 今回Emailのみを連携対象としていますが、例えばuser名も連携したいと言う場合もあると思います.その場合は、authのparameters.jsonを修正ください
  - どんな項目が連携項目かはCognitoのユーザプール > フェデレーション > 属性マッピングで確認できます

```amplify/backend/auth/test1ec8a379/parameters.json
{
-- 中略 --
    "hostedUIProviderMeta": "[{\"ProviderName\":\"Facebook\",\"authorize_scopes\":\"email,public_profile\",\"AttributeMapping\":{\"email\":\"email\",\"username\":\"id\"}}]", # こちらに\"name\":\"name\"を追加
    "oAuthMetadata": "{\"AllowedOAuthFlows\":[\"code\"],\"AllowedOAuthScopes\":[\"phone\",\"email\",\"openid\",\"profile\",\"aws.cognito.signin.user.admin\"],\"CallbackURLs\":[\"https://localhost:8081/login/\",\"https://www.test.com/login/\"],\"LogoutURLs\":[\"https://localhost:8081/login/\",\"https://www.test.com/login/\"]}",
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/a6b29687-c195-828f-26ee-b33186b7cf42.png)

- Cognitoのユーザープールでアプリの統合 > ドメイン名からAmazon Cognitoドメインを控えておきます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/7f2d51bb-ab15-40df-4c7c-c92ce1fe3494.png)


## フロントアプリ側の設定
### リダイレクト先の修正
- [こちら](https://qiita.com/jacoyutorius/items/5fc27ca70ef8b672a96b)で書かれているようにLocalとProductionなど複数のリダイレクト先を設定すると、アプリ側でうまくリダイレクトができません.
- そこでAmplifyのconfig情報であるaws-exports.jsを読み込む場所でホストに合致するリダイレクト先を設定するようにします

```main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import Amplify, * as AmplifyModules from 'aws-amplify'
import { AmplifyPlugin } from 'aws-amplify-vue'
import awsConfig from './aws-exports'
import store from './store'
require('@/assets/sass/styles.scss')

// redirectURLが複数存在する場合、エラーになるため対象ドメインに絞り込む
if (awsConfig.oauth.redirectSignIn.includes(',')) {
  const filterHost = url => new URL(url).host === location.host
  awsConfig.oauth.redirectSignIn = awsConfig.oauth.redirectSignIn
    .split(',')
    .filter(filterHost)
    .shift()
  awsConfig.oauth.redirectSignOut = awsConfig.oauth.redirectSignOut
    .split(',')
    .filter(filterHost)
    .shift()
}
Amplify.configure(awsConfig)
-- 中略 --
```

### ログイン画面の実装
- Amplifyが用意しているコンポーネントは使わず、自前で作る例です

```LoginPage.vue
<template>
  <button @click='loginByFacebook'>
    <span>Facebookでログイン</span>
  </button>
</template>

<script>
import { Hub } from 'aws-amplify'
import awsExport from '../../aws-exports'

export default {
  mounted () {
    this.hubSocialLogin()
  },
  destroyed  () {
    if (Hub) Hub.remove('auth')
  },
  methods: {
    hubSocialLogin () {
      Hub.listen('auth', (data) => {
        try {
          switch (data.payload.event) {
            case 'signIn':
              router.push({ path: '/hoge' })
              break
            case 'signIn_failure':
              break
            default:
              break
          }
        } catch (error) {
        }
      })
    },
    loginByFacebook () {
      const CognitoDomain = awsExport.oauth.domain
      const CognitoAppClientID = awsExport.aws_user_pools_web_client_id
      const RedirectURI = awsExport.oauth.redirectSignIn
      const url = `https://${CognitoDomain}/oauth2/authorize?identity_provider=Facebook&redirect_uri=${RedirectURI}&response_type=code&client_id=${CognitoAppClientID}`
      window.location.assign(url)
    }
  }
}

</script>
```

## CI/CDの設定
- ここがかなり苦労しました。今の状態でPreview環境や本番環境でデプロイするとエラーになります。

```
2019-11-25T16:28:56.016Z [INFO]: # Executing command: amplifyPush --environment prod
2019-11-25T16:28:56.020Z [INFO]: # Getting Amplify CLI Cloud-Formation stack info from environment cache
2019-11-25T16:28:56.026Z [INFO]: # Start initializing Amplify environment: prod
2019-11-25T16:28:56.028Z [INFO]: # Initializing new Amplify environment: prod (amplify init)
2019-11-25T16:29:16.860Z [INFO]: Note: It is recommended to run this command from the root of your app directory
2019-11-25T16:29:21.331Z [WARNING]: - Initializing your environment: prod
2019-11-25T16:29:21.786Z [WARNING]: ✔ Initialized provider successfully.
2019-11-25T16:29:21.832Z [WARNING]: ✖ There was an error initializing your environment.
2019-11-25T16:29:21.833Z [INFO]: init failed
2019-11-25T16:29:21.834Z [INFO]: Error: auth headless init is missing the following inputParams facebookAppIdUserPool, facebookAppSecretUserPool, googleAppIdUserPool, googleAppSecretUserPool
at updateConfigOnEnvInit (/root/.nvm/versions/node/v10.16.0/lib/node_modules/@aws-amplify/cli/node_modules/amplify-category-auth/provider-utils/awscloudformation/index.js:315:15)
at /root/.nvm/versions/node/v10.16.0/lib/node_modules/@aws-amplify/cli/node_modules/amplify-category-auth/index.js:241:28
at /root/.nvm/versions/node/v10.16.0/lib/node_modules/@aws-amplify/cli/node_modules/promise-sequential/index.js:16:18
at process._tickCallback (internal/process/next_tick.js:68:7)
```

- [こちら](https://github.com/aws-amplify/amplify-console/issues/206#issuecomment-583839878)を参考にして対処しました

```amplify.yml
version: 0.1
backend:
  phases:
    build:
      commands:
        - chmod u+x ./amplifypush.sh
        - ./amplifypush.sh
frontend:
  phases:
    preBuild:
      commands:
        - yarn install
    build:
      commands:
        - yarn run build
  artifacts:
    baseDirectory: dist
    files:
      - "**/*"
  cache:
    paths:
      - node_modules/**/*
```

```amplifypush.sh
#!/usr/bin/env bash
set -e
IFS='|'
help_output () {
    echo "usage: amplify-push <--environment|-e <name>> <--simple|-s>"
    echo "  --environment  The name of the Amplify environment to use"
    echo "  --simple  Optional simple flag auto-includes stack info from env cache"
    exit 1
}

init_env () {
    ENV=$1
    AMPLIFY=$2
    PROVIDERS=$3
    CODEGEN=$4
    AWSCONFIG=$5
    CATEGORIES=$6
    echo "# Start initializing Amplify environment: ${ENV}"
    if [[ -z ${STACKINFO} ]];
    then
        echo "# Initializing new Amplify environment: ${ENV} (amplify init)"
        amplify init --amplify ${AMPLIFY} --providers ${PROVIDERS} --codegen ${CODEGEN} --categories ${CATEGORIES} --yes;
        echo "# Environment ${ENV} details:"
        amplify env get --name ${ENV}
    else
        echo "STACKINFO="${STACKINFO}
        echo "# Importing Amplify environment: ${ENV} (amplify env import)"
        amplify env import --name ${ENV} --config "${STACKINFO}" --awsInfo ${AWSCONFIG} --categories ${CATEGORIES} --yes;
        echo "# Initializing existing Amplify environment: ${ENV} (amplify init)"
        amplify init --amplify ${AMPLIFY} --providers ${PROVIDERS} --codegen ${CODEGEN} --categories ${CATEGORIES} --yes;
        echo "# Environment ${ENV} details:"
        amplify env get --name ${ENV}
    fi
    echo "# Done initializing Amplify environment: ${ENV}"
}

ENV=""
IS_SIMPLE=false
POSITIONAL=()
while [[ $# -gt 0 ]]
do
    key="$1"
    case ${key} in
        -e|--environment)
            ENV=$2
            shift
            ;;
        -r|--region)
            REGION=$2
            shift
            ;;
        -s|--simple)
            IS_SIMPLE=true
            shift
            ;;
        *)
            POSITIONAL+=("$1")
            shift
            ;;
    esac
done

set -- "${POSITIONAL[@]}"

# if no provided environment name, use default env variable, then user override
if [[ ${ENV} = "" ]];
then
    ENV=${AWS_BRANCH}
fi
if [[ ${USER_BRANCH} != "" ]];
then
    ENV=${USER_BRANCH}
fi

# Check valid environment name
if [[ -z ${ENV} || "${ENV}" =~ [^a-zA-Z0-9\-]+ ]] ; then help_output ; fi

AWSCONFIG="{\
\"configLevel\":\"project\",\
\"useProfile\":true,\
\"profileName\":\"default\"\
}"

AMPLIFY="{\
\"envName\":\"${ENV}\"\
}"

PROVIDERS="{\
\"awscloudformation\":${AWSCONFIG}\
}"

CODEGEN="{\
\"generateCode\":false,\
\"generateDocs\":false\
}"

AUTHCONFIG="{\
\"facebookAppIdUserPool\":\"アプリID\",\
\"facebookAppSecretUserPool\":\"シークレットキー\"\
}"

CATEGORIES="{\
\"auth\":$AUTHCONFIG\
}"

# Handle old or new config file based on simple flag
if [[ ${IS_SIMPLE} ]];
then
    echo "# Getting Amplify CLI Cloud-Formation stack info from environment cache"
    export STACKINFO="$(envCache --get stackInfo)"
    init_env ${ENV} ${AMPLIFY} ${PROVIDERS} ${CODEGEN} ${AWSCONFIG} ${CATEGORIES}
    echo "# Store Amplify CLI Cloud-Formation stack info in environment cache"
    STACKINFO="$(amplify env get --json --name ${ENV})"
    envCache --set stackInfo ${STACKINFO}
    echo "STACKINFO="${STACKINFO}
else
    # old config file, above steps performed outside of this script
    init_env ${ENV} ${AMPLIFY} ${PROVIDERS} ${CODEGEN} ${AWSCONFIG} ${CATEGORIES}
fi
```

# Facebookの設定2
- Facebookログイン機能を追加します
- ダッシュボードの「製品の追加」から「Facebookログイン」を選択します
- 左メニューのFacebookログインの設定を選択し、有効なOAuthリダイレクトURIにCognitoのドメインで控えておいた値を元に `https://<ドメイン>.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse` と入力します.こちらはLocalと本番環境それぞれ入力できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/0a68191c-6314-55e9-35af-a26119f48027.png)

- 上部のアプリIDの隣にある開発中のタブをライブにします.
  - プライバシーポリシーのURLを設定していないと、ライブにできないのでご注意ください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/2792316d-443d-a327-9bbc-2e6ec6dcee96.png)

# 確認
Facebookでログインできることを確認します.
またCognito側でアカウントのステータスがEXTERNAL_PROVIDERでユーザプールが生成されていることも確認できると思います

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/7ebb99df-1ff9-5d94-0d14-0f0e6a0dd194.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202761/234969c8-f312-bf2e-88dc-6001a76fbe3b.png)

# 参考
- [AmazonCognitoでGoogle/Facebook認証してトークンを取得する](https://qiita.com/is_ryo/items/a437225115b27e05c8ee#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)
- [Amplify auth は複数のコールバックURLに対応していない](https://qiita.com/jacoyutorius/items/5fc27ca70ef8b672a96b)