Cognitoのユーザープールから無理やりAuth0にオートマイグレーションしてみる

2021.02.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Auth0には自前のカスタムDBからユーザーを自動移行できるソリューションが用意されています。

ASP.NET Membership Provider, MongoDB, MySQL, Oracle, PostgreSQL, SQLServer, Windows Azure SQL Database などから移行できるようにカスタムDBのスクリプトテンプレートが用意されています。

今回はこの自前のカスタムDBをCognitoのユーザープールにして、Auth0に移行できるか検証します。

前提

Cognito

  • Cognitoのユーザープール構築済み
  • ユーザー作成済みで、パスワードの変更も終了済み
  • エンドユーザーのサインイン
    • ユーザー名
    • 検証済みのEメールアドレスでのサインインも許可
  • 必須の標準属性
    • email

Auth0

  • 移行用のDatabase作成済み
  • 設定
    • Requires Username をオンにする(emailかusernameでログインできるように)
    • Import Users to Auth0 をオンにする
    • Use my own database をオンにする

この条件下でやっていこうと思います。

※ 今回試したのはログインスクリプトのみです。Get Userスクリプトに関しては検証していませんのでご注意を

移行の流れ

以下のシナリオで試します。

  1. ユーザーがアプリケーションのログインページにアクセス
  2. メールアドレス(ユーザー名) と パスワードを入力
  3. Auth0のカスタムDBスクリプトから入力されたパラメータを使ってcognitoユーザープールへ認証リクエストを送信
  4. cognitoユーザープールから返却されたトークンを検証
  5. 検証OKならインポート完了

MongoDBやMySQLなどのスクリプトテンプレートでは、DBから取得したパスワードハッシュと入力されたパスワードハッシュを比較していましたが、

cognitoユーザープールではできなそうなので、認証リクエストが正しく終わった後に返却されたJWTのトークンを検証することにしました。

カスタムログインスクリプト

httpリクエストを作って認証するようにしてみました。

function login(email, password, callback) {
  const request = require('request');
  const jwt = require('jsonwebtoken');

  const clientId = configuration.clientId;
  const userPoolId = configuration.userPoolId;

  let userData = {
        "AuthParameters": {
            "USERNAME": email,
            "PASSWORD": password
        },
        "AuthFlow": "USER_PASSWORD_AUTH",
        "ClientId": clientId
  }

  let options = {
        'method': 'POST',
        'url': 'https://cognito-idp.ap-northeast-1.amazonaws.com/',
        'headers': {
            'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth',
            'Content-Type': 'application/x-amz-json-1.1'
        },
        body: JSON.stringify(userData)
  };

  request(options, function (error, response) {
   //その後の処理
  }
}

configuration.clientId, configuration.userPoolIdは カスタムスクリプトのSettingsで値を入れておきます。

clientIdは cognitoユーザープールの アプリクライアント ID

userPoolIdは cognitoユーザープールの プール ID です。

request処理の続きですが、ここでtokenを取得し、JWTの検証を行なっています。

request(options, function (error, response) {
        if (error) throw new Error(error);
        let resJson = JSON.parse(response.body);
        // IDトークン
        const token = resJson.AuthenticationResult.IdToken;

        const pem = configuration.PEM.replace(/\\n/g, '\n');
        
        // jwtの検証
        jwt.verify(token, pem, { algorithms: ['RS256'] }, function(err, decodedToken) {
            if(err !== null){
                console.error(err);
            }else{
                // other verify

                if(decodedToken.aud !== clientId) {
                    console.error(err);
                    return callback(err);
                }

                if(decodedToken.iss !== `https://cognito-idp.ap-northeast-1.amazonaws.com/${userPoolId}`) {
                    console.error(err);
                    return callback(err);
                }

                const nowTime = Math.floor(new Date().getTime() / 1000);
                if(nowTime > decodedToken.exp) {
                    console.error(err);
                    return callback(err);
                }

                //console.log(decodedToken);
                console.log("Success");
                callback(null, {
                    username: email, // Requires UsernameがONの時
                    user_id: decodedToken.sub,
                    email: decodedToken.email
                });
            }
        });
});

認証がOKだったらcognitoからアクセストークン、リフレッシュトークン、IDトークンが返ってくるはずです。

その後、 jsonwebtokenライブラリを使用して検証を行っています。

cognitoのjwt検証に関しては以下のページが参考になります。

署名の検証、クレームの検証をやってみました。

クレームですが、

トークンの有効期限, audクレームは、 Amazon Cognito ユーザープールのアプリクライアントIDと一致, issのクレームは、ユーザープールと一致する というのを確認してみました。

署名の検証でハマったこと

AWSのドキュメントではJWKをpemに変換してverifyを呼び出していましたが、Auth0のカスタムスクリプトでは jwk-to-pem ライブラリが 組み込まれていませんのでどうしようか迷いました。

jwkの取得は、

https://cognito-idp.ap-northeast-1.amazonaws.com/<<your user pool id>>/.well-known/jwks.json で取得できます。

どうしたかと言うと、取得したjwkを使ってpemをあらかじめ作成し、カスタムスクリプトの設定値として保存しました。

かなり強引な方法ですw

JWK to PEM Convertor online というサイトでpemに変換できます。

変換した文字列の改行を \n に変換して保存しました。

検証が終わった後のコールバックには、

username, user_id, emailを返却します。

callback(null, {
                    username: email, // Requires UsernameがONの時
                    user_id: decodedToken.sub,
                    email: decodedToken.email
                });

usernameでログインした時には、 emailパラメーターに入力値が入ります。

user_idはidトークンのsubを使用しました。

emailはidトークンのemailですね。

移行できるか試してみる

デバッグ

ログインスクリプトを保存した後、`TRY`ボタンを押してデバッグができます。

cognitoユーザープールに存在するユーザーの認証情報で試します。

正しく動作していると、

callbackに指定した値が表示されます。

ここではまだAuth0にユーザーは作成されません。

移行

Try Connectionをクリックして、アプリのログイン画面を起動させます。

メールアドレスでログイン

cognitoユーザープールに存在するユーザーの認証情報でログインすると、

こんな感じでログインが成功します。

もちろんユーザーも作成されています。

Usernameでログイン

このようなユーザーでログインしてみます。

やり方はメールアドレスの時と同じですね。

成功するとIt Works!の画面が表示されます。

ユーザーも作成されています。

まとめ

CognitoのユーザープールからAuth0にオートマイグレーションできないか検証してみました。

httpで認証リクエストを送信しパスワードが正しいか確認、jwtの検証がOKだったら移行可能とする というちょっと無理やりな仕様にしてやったのですが、 スクリプトは動きました。

Auth0公式で対応してくれるのが良いのですが、cognitoからの移行を考えている方がいれば参考にしていただきたいと思います。

※ 今回検証に使ったコードは以下に公開しています。

https://gist.github.com/y-o-u/a59172e1641462b2f6082de129120948

awsのsdkを使った場合