MongoDB Atlas上のユーザをAuth0にシームレスに移行する

# はじめに
MongoDB Atlas上に作成したUser Data StoreとAuth0を接続して既存のユーザをAuth0に移行する手順をご紹介します。この記事はこちらの[原文](https://auth0.com/blog/auth0-tutorials-using-mongodb-atlas-as-a-custom-database/)を参照して作成しています。検証はNode.js, NPMのインストール、Auth0の無料アカウントの取得とテナントの作成が完了していることが前提となっています。まだの方は[こちら](https://qiita.com/furuth/items/8826b66a3ec2b2b14222)の記事を参照の上ご準備をお願いします。

# 検証環境
- **端末のOS** : `macOS Mojave 10.14.5`
- **MongoDB Atlas** : `Version 4.0.9`
- **Mongo Shell** : `Version 4.0.9`
- **Node.js** : `10.15.3`
- **npm** : `6.4.1`


# 作業手順
## Mongo側
端末にMongo Shellをインストールします。

```console
$ brew install mongodb/brew/mongodb-community-shell
```
<br>

Chromeで[MongoDB Atlas](https://www.mongodb.com/cloud/atlas)にアクセスしてフリーアカウントを作成します。
<img src="https://i.imgur.com/fkGoXJr.png" width=70%>
<br>

右上の"New Project"を押して新しいプロジェクトを作成します。"Name Your Project"任意の名前を入力して"Next"を押します。
<img src="https://i.imgur.com/gmJG5Ng.png" width=70%>
<br>

"Create Project"を押します。
<img src="https://i.imgur.com/T5XKYPl.png" width=70%>
<br>

"Build a Cluster"を押して作成したプロジェクトにクラスタを作成します。
<img src="https://i.imgur.com/Uu8ANM1.png" width=70%>
<br>

全てデフォルトのまま画面下の"Create Cluster"を押します。
<img src="https://i.imgur.com/Nsad6Ae.png" width=70%>
<br>

左ペインの"Database Access"をクリック、画面右上の"ADD NEW USER"を押して新しい管理用ユーザを作成します。
<img src="https://i.imgur.com/EFH1jEJ.png" width=70%>
<br>

任意のユーザ名・パスワードを入力、User Privilegesは"Read and write to any database"を選択して"Add User"を押します。**パスワードは10文字以上かつ英数字のみを指定します。Special Characterを指定すると動作しないことが確認されています。**
<img src="https://i.imgur.com/cwmHdH2.png" width=70%>
<br>

左ペインの"Network Access"をクリック、画面右上の"+ADD IP ADDRESS"を押してWhitelisting IP addressを追加します。
<img src="https://i.imgur.com/nwkuodr.png" width=70%>
<br>

**この記事では検証目的のため0.0.0.0/0を指定しています。本番環境で設定する場合は必要最低限のIPを指定して下さい。**
<img src="https://i.imgur.com/RQIHTGY.png" width=70%>
<br>

左ペインの"Clusters"をクリック、"CONNECT"->"Connect with the Mongo Shell"を選択してMongo Shellから作成したクラスタに接続します。
<img src="https://i.imgur.com/JHlqPhW.png" width=70%>
<br>

"Run your connection string in your command line"をコピーしてconsoleにペーストします。
<img src="https://i.imgur.com/7byOhkW.png" width=70%>
<br>

管理用ユーザ作成時に設定したパスワードを入力して接続します。

```console
$  mongo "mongodb+srv://cluster0-rt8x2.mongodb.net/test" --username auth0-custom-db-user                                                                                 15:59:03
MongoDB shell version v4.0.9
Enter password:
connecting to: mongodb://cluster0-shard-00-00-rt8x2.mongodb.net.:27017,cluster0-shard-00-01-rt8x2.mongodb.net.:27017,cluster0-shard-00-02-rt8x2.mongodb.net.:27017/test?authSource=admin&gssapiServiceName=mongodb&replicaSet=Cluster0-shard-0&ssl=true
2019-06-03T15:59:16.760+0900 I NETWORK  [js] Starting new replica set monitor for Cluster0-shard-0/cluster0-shard-00-00-rt8x2.mongodb.net.:27017,cluster0-shard-00-01-rt8x2.mongodb.net.:27017,cluster0-shard-00-02-rt8x2.mongodb.net.:27017
2019-06-03T15:59:17.459+0900 I NETWORK  [js] Successfully connected to cluster0-shard-00-00-rt8x2.mongodb.net.:27017 (1 connections now open to cluster0-shard-00-00-rt8x2.mongodb.net.:27017 with a 5 second timeout)
2019-06-03T15:59:17.459+0900 I NETWORK  [ReplicaSetMonitor-TaskExecutor] Successfully connected to cluster0-shard-00-01-rt8x2.mongodb.net.:27017 (1 connections now open to cluster0-shard-00-01-rt8x2.mongodb.net.:27017 with a 5 second timeout)
2019-06-03T15:59:17.621+0900 I NETWORK  [js] changing hosts to Cluster0-shard-0/cluster0-shard-00-00-rt8x2.mongodb.net:27017,cluster0-shard-00-01-rt8x2.mongodb.net:27017,cluster0-shard-00-02-rt8x2.mongodb.net:27017 from Cluster0-shard-0/cluster0-shard-00-00-rt8x2.mongodb.net.:27017,cluster0-shard-00-01-rt8x2.mongodb.net.:27017,cluster0-shard-00-02-rt8x2.mongodb.net.:27017
2019-06-03T15:59:18.299+0900 I NETWORK  [ReplicaSetMonitor-TaskExecutor] Successfully connected to cluster0-shard-00-00-rt8x2.mongodb.net:27017 (1 connections now open to cluster0-shard-00-00-rt8x2.mongodb.net:27017 with a 5 second timeout)
2019-06-03T15:59:18.299+0900 I NETWORK  [js] Successfully connected to cluster0-shard-00-01-rt8x2.mongodb.net:27017 (1 connections now open to cluster0-shard-00-01-rt8x2.mongodb.net:27017 with a 5 second timeout)
2019-06-03T15:59:20.101+0900 I NETWORK  [ReplicaSetMonitor-TaskExecutor] Successfully connected to cluster0-shard-00-02-rt8x2.mongodb.net:27017 (1 connections now open to cluster0-shard-00-02-rt8x2.mongodb.net:27017 with a 5 second timeout)
Implicit session: session { "id" : UUID("fc24b722-7351-441a-b772-a9143054f0f8") }
MongoDB server version: 4.0.9
Error while trying to show server startup warnings: user is not allowed to do action [getLog] on [admin.]
MongoDB Enterprise Cluster0-shard-0:PRIMARY>
```

Dababase名:auth0-integration、Collection名:usersで各々作成します。

```console
> use auth0-integration;
> db.createCollection('users');
```

Collectionが作成されたか確認します。

```console
> show collections
users
```

サンプルのログインユーザを投入します。

```console
> db.users.insert(
   [
     { username: "a.user@spam4.me", password: "som3Passw0rd!" },
     { username: "another.user@spam4.me", password: "newPassw0rd!" },
     { username: "yet.another.user@spam4.me", password: "an0th3rPwd!" }
   ]
)
```

正常に投入できたか確認します。

```console
> db.users.find();
{ "_id" : ObjectId("5cf4c9c1e6e0be4e6822b4c8"), "username" : "a.user@spam4.me", "password" : "som3Passw0rd!" }
{ "_id" : ObjectId("5cf4c9c1e6e0be4e6822b4c9"), "username" : "another.user@spam4.me", "password" : "newPassw0rd!" }
{ "_id" : ObjectId("5cf4c9c1e6e0be4e6822b4ca"), "username" : "yet.another.user@spam4.me", "password" : "an0th3rPwd!" }
```

<br>

## Auth0側
左ペインの"Connections"をクリック、画面右上の"+CREATE DB CONNECTION"を押してCustom Database Connectionを作成します。
<img src="https://i.imgur.com/zrUmSPR.png" width=70%>
<br>

任意の名前を入力して画面下の"Create"を押します。
<img src="https://i.imgur.com/JSXRyOS.png" width=70%>
<br>

"Custom Database"タブを選択、"Use my own database"のフリップスイッチをオンにして作成したMongoDB Atlasとの接続を有効にします。
<img src="https://i.imgur.com/fOHH2bZ.png" width=70%>
<br>

Loginスクリプトを修正して"SAVE"ボタンを押します。
- Auth0はMongo Library 3.1.4のみサポート(2019年6月現在)しているためバージョンを明示的に指定します。
- dbHostの形式は"xxxx.mongodb.net"です。
- **この記事では検証目的のためパスワードは平文で指定しています。本番環境では暗号化の処理を追加して下さい。**

```jsx
function login(email, password, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "auth0-customdb-user";
  const dbPwd = "xxxx";
  const dbHost = "cluster0-kft70.mongodb.net";
  const dbName = "auth0-integration";
  const usersCollection = "users";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    if (err) return callback(err);

    const collection = client.db(dbName).collection(usersCollection);

    collection.findOne({username: email, password}, (err, user) => {
      if (err) return callback(err);
      if (!user) return callback("User not found.");

      const profile  = {
        ...user,
        user_id: user.username
      };
      callback(null, profile);
      client.close();
    });
  });
}
```

"TRY"ボタンを押して、Collectionに投入したユーザのID/Passwordを入力、認証が成功するとJSON形式のレスポンスが返ってきます。
<img src="https://i.imgur.com/6J9S8G6.png" width=70%>
<br>

"Settings"タブをクリック、"Import Users to Auth0"フリップスイッチをオンにしてMongoDB Atlas上のユーザをAuth0に移行することを許可します。
<img src="https://i.imgur.com/iRaCc4H.png" width=70%>
<br>

"Custom Database"タブをクリック、"Database Action Scripts"で"Get User"をクリックしてスクリプトを修正、"SAVE"ボタンを押します。

```jsx
function getByEmail(email, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "auth0-customdb-user";
  const dbPwd = "xxxx";
  const dbHost = "cluster0-kft70.mongodb.net";
  const dbName = "auth0-integration";
  const usersCollection = "users";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    if (err) return callback(err);

    const collection = client.db(dbName).collection(usersCollection);

    collection.findOne({username: email}, (err, user) => {
      if (err) return callback(err);
      if (!user) return callback("User not found.");

      delete user.password;
      const profile  = {
        ...user,
        user_id: user.username
      };
      callback(null, profile);
      client.close();
    });
  });
}
```

## 動作確認
左ペインの"Applications"をクリック、画面右上の　”+CREATE APPLICATION”を押します。
<img src="https://i.imgur.com/srvA63p.png" width=70%>
<br>

任意の名前を入力、"Choose an application type"で"Single Page Web Applications"を選択して"CREATE"ボタンを押します。
<img src="https://i.imgur.com/WkR4QgK.png" width=70%>

画面右下の"VIEW ON GITHUB"をクリックしてSample Repoを端末にCloneします。
<img src="https://i.imgur.com/tVulnYM.png" width=70%>
<br>

"Settings"タブをクリック、"Allopwed Callback URLs"に"`http://localhost:3000/callback`"、"Allowed Web Origins"に"`http://localhost:3000`"を入力して画面下の"SAVE CHANGES"を押します。
<img src="https://i.imgur.com/tOoKUaE.png" width=70%>
<br>

auth0-react-samples/01-Login/src/Auth/auth0-variables.js.exampleをコピーして同一ディレクトリにauth0-variables.jsを作成します。

```console
$ cp auth0-variables.js.example auth0-variables.js
```

auth0-variables.jsを開いてdomainとclientIdを指定します。domainとclientIdは"Settings"タブで確認できます。

```jsx
export const AUTH_CONFIG = {
  domain: 'mokomoko.auth0.com',
  clientId: 'mB44RrIUvRNRpv0VQH1OOnlWicd4xHDP',
  callbackUrl: 'http://localhost:3000/callback'
}
```

<img src="https://i.imgur.com/i6LXDkF.png" width=70%>
<br>

Readmeに従いサンプルアプリケーションを起動します。

```console
$ pwd
/tmp/auth0-react-samples/01-Login
$ npm install
$ npm start
```
<br>

既存のユーザでログインします。
<img src="https://i.imgur.com/FEiMwdM.png" width=70%>
<br>

左ペインのUsersをクリックしてユーザがAuth0に移行されたことを確認します。
<img src="https://i.imgur.com/bZeeujE.png" width=70%>
<br>

# おわりに
この記事では既存のユーザをAuth0に移行する手順をご紹介していますが、Auth0は既に利用されている外部のUser Data Storeを使い続けることも可能です。その場合は"Import Users to Auth0"フリップスイッチをオフにして、Login以外のスクリプト(例/ユーザ作成・削除、パスワード変更)をAuth0のダッシュボードで設定します。