🦔

FlutterFlowでAmazon Cognitoを使った認証をするデモ

2024/01/27に公開2

はじめに

GenAiの菅原です。
FlutterFlowで作ったアプリを業務システムに組み込んでいくときに、認証基盤を既存のものを利用したいというご相談をいただくことがよくあります。今回の記事ではFlutterFlowでAmazon Cognitoを使った認証を行う方法について簡単に記載します。

デモで実装する要件

デモで実装する要件は以下の通り。

  • Cognitoのユーザープールを利用する
  • Flutterアプリ側からユーザー登録、メールアドレスの検証、ログインを行う
  • メールアドレス、パスワードを使ったベーシックな認証を行う
  • Cognitoから発行されたアクセストークンをFlutter側で保持する

方針

通常のFlutter開発だとAWSのリソースにアクセスするときはAmplify SDKを使ってアクセスを行いますが、FlutterFlowにはAmplify SDKを組み込めません。したがって、FlutterFlowから、AWS側に作成したLambda関数経由でCognitoの各種機能を利用する方針としています。

構成

使用するサービスは以下の通り。

  • Amazon Cognito
  • AWS Lambda
  • FlutterFlow

なお、簡易化のためにLambda(と関数URL)のみを使用していますが、きちんとAPIのエンドポイントを設計したい方は、API Gatewayを使って作りましょう。

1.Cognitoユーザープールのセットアップ

Amazon Cognitoのコンソール画面から以下の通りユーザープールを作成します。デモのため、最低限の設定にしています。

  • Cognito ユーザープールのサインインオプション情報を、Eメールに設定する
  • パスワードポリシーモードはデフォルトを選択
  • 多要素認証 - MFAの強制は、「MFAなし」を選択
  • ユーザーアカウントの復旧 - セルフサービスのアカウントの復旧を有効化はオン
  • ユーザーアカウント復旧メッセージの配信方法は「Eメールのみ」にする
  • セルフサービスのサインアップについて、「自己登録を有効化」をオンにする
  • 属性検証とユーザーアカウントの確認「Cognito が検証と確認のためにメッセージを自動的に送信することを許可」をオンにする
  • 検証する属性は「Eメールのメッセージを送信、Eメールアドレスを検証」を選択
  • 属性変更の確認について、「未完了の更新があるときに元の属性値をアクティブに保つ」をオンにする
  • 未完了の更新があるときのアクティブな属性値は「Eメールアドレス」を選択
  • 必須の属性はemailのみ
  • Eメールについて、「Cognito で Eメールを送信」を選択し、他はデフォルトのままとする
  • ユーザープール名に、任意のユーザープール名を入力する
  • 「CognitoのホストされたUIを使用」はオフのままにする
  • アプリケーションタイプは「パブリッククライアント」を選択する
  • アプリケーションクライアント名は任意の名前を入力する
  • 「クライアントのシークレットを生成する」を選択する
  • 認証フローは「ALLOW_USER_PASSWORD_AUTH」を選択する
  • 「トークンの取り消しを有効化」をオンにする
  • 「ユーザー存在エラーの防止」をオンにする

上記の設定の上、ユーザプールを作成します。

2.Lambda関数の作成

  • 必要な処理の数だけ、Lambda関数を作成します。
    • 「関数URLを有効化」をオンにする
    • 認証タイプは「NONE」にする
    • 呼び出しモードは「BUFFERED」を選択
    • 今回ランタイムはNode.js 20.xでやっています。
    • 実行ロールはCognitoへのアクセスができるものを選択します。

ユーザー登録

import { CognitoIdentityProviderClient, SignUpCommand } from "@aws-sdk/client-cognito-identity-provider";
import crypto from 'crypto';

const client = new CognitoIdentityProviderClient({ region: "us-east-1" });

function generateSecretHash(username, clientId, clientSecret) {
    return crypto.createHmac('SHA256', clientSecret)
                 .update(username + clientId)
                 .digest('base64');
}
async function signUp(event) {
    const body = JSON.parse(event.body);

    const { email, password } = body;
    const clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxx";  // CognitoユーザープールのクライアントID
    const clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXX"; // Cognitoユーザープールのクライアントシークレット。環境変数に定義しておくとなお良いと思います
 
    //     環境変数での実装は以下。設定->環境変数から値を設定してください。
    //     const clientId = process.env.CLIENT_ID;
    //     const clientSecret = process.env.CLIENT_SECRET;

    const secretHash = generateSecretHash(email, clientId, clientSecret);

    const params = {
        ClientId: clientId,
        SecretHash: secretHash,
        Username: email,
        Password: password,
        UserAttributes: [
            {
                Name: "email",
                Value: email
            }
        ]
    };

    const command = new SignUpCommand(params);

    try {
        const data = await client.send(command);
        console.log(data);
        return { status: 'SUCCESS', data: data };
    } catch (error) {
        console.error(error);
        return { status: 'ERROR', error: error };
    }
}

export { signUp as handler };
  • 関数URLに、POSTで動作確認してみて、UserSubなどが含まれたレスポンスが返ってきたら成功です。
  • ユーザー登録成功時、メールアドレス宛に確認コードが書いたメールが届きます。
curl -X POST -H "Content-Type: application/json" -d '{"email": "<メールアドレス>", "password": "<パスワード>"}' https://<関数URL>

ユーザー登録後のメールアドレス確認

import { CognitoIdentityProviderClient, ConfirmSignUpCommand } from "@aws-sdk/client-cognito-identity-provider";
import crypto from 'crypto';

function generateSecretHash(username, clientId, clientSecret) {
    return crypto.createHmac('SHA256', clientSecret)
                 .update(username + clientId)
                 .digest('base64');
}

const client = new CognitoIdentityProviderClient({ region: "us-east-1" });

async function confirmSignUp(event) {
    const { username, confirmationCode } = JSON.parse(event.body);
    const clientId = process.env.CLIENT_ID;
    const clientSecret = process.env.CLIENT_SECRET;

    const secretHash = generateSecretHash(username, clientId, clientSecret);

    const params = {
        ClientId: clientId,
        SecretHash: secretHash,
        Username: username,
        ConfirmationCode: confirmationCode
    };

    try {
        const command = new ConfirmSignUpCommand(params);
        await client.send(command);
        return {
            statusCode: 200,
            body: JSON.stringify({ message: "Signup confirmed successfully." })
        };
    } catch (error) {
        console.error(error);
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message })
        };
    }
}

export { confirmSignUp as handler };

  • 関数URLに、POSTで動作確認してみてください。
curl -X POST -H "Content-Type: application/json" -d '{"username": "<メールアドレス>", "confirmationCode": "<確認コード>"}' https://<関数URL>

サインイン

import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider";
import crypto from 'crypto';

const client = new CognitoIdentityProviderClient({ region: "us-east-1" });

function generateSecretHash(email, clientId, clientSecret) {
    return crypto.createHmac('SHA256', clientSecret)
                 .update(email + clientId)
                 .digest('base64');
}

async function signIn(event) {
    const { email, password } = JSON.parse(event.body);
    const clientId = process.env.CLIENT_ID;
    const clientSecret = process.env.CLIENT_SECRET;
    const secretHash = generateSecretHash(email, clientId, clientSecret);

    const params = {
        AuthFlow: "USER_PASSWORD_AUTH",
        ClientId: clientId,
        AuthParameters: {
            USERNAME: email,
            PASSWORD: password,
            SECRET_HASH: secretHash
        },
    };

    try {
        const command = new InitiateAuthCommand(params);
        const response = await client.send(command);
        console.log(response);
        return {
            statusCode: 200,
            body: JSON.stringify(response.AuthenticationResult)
        };
    } catch (error) {
        console.error(error);
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message })
        };
    }
}

export { signIn as handler };

  • 関数URLに、POSTで動作確認してみて、各種トークンが返ってきたら成功です。
curl -X POST -H "Content-Type: application/json" -d '{"email": "<メールアドレス>", "password": "<パスワード>"}' https://<関数URL>

3.FlutterFlow側の実装

  1. API Callsから上記の各種エンドポイントにアクセスする設定をします。詳細は割愛します。
  2. App Settingsより、Authの設定を行います。Authentication TypeはCustomを選択します。他は任意で設定してください。
  3. ページを作成します。最低限、以下が必要です。
  • ユーザー登録画面
  • ログイン画面
  • 確認コードの入力画面
  • ログイン後の画面
  1. (ユーザー登録ページ)ユーザー登録アクションの実装をします。

    やっていることとしては、最初にAPIを使ってLambdaにユーザー情報を送信してユーザー登録をして、成功時に確認コード入力画面に遷移します。

  2. (確認コード入力画面)確認コード入力アクションの実装をします。

    やっていることとしては、APIを使ってLambdaにユーザーID(メールアドレス)と確認コードを送り、Cognitoでメールアドレスを確認しています。

  3. (ログイン画面)ログインアクションの実装をします。

    やっていることとしては、APIを使ってLambdaにユーザー情報を送信して、認証を行なっています。成功時にCustom AuthenticationのLoginを使い、ユーザーデータを保存しています。(ブラウザではlocal Storageに保存されます。)

おわりに

上記のような形で、FlutterFlowではWeb APIを経由することで、Cognitoの各種機能を扱うことができます。同様の方法でパスワードリセットや、パスワード変更、ユーザ情報の変更なども実装できますので試してみてください。

また、今回は省きましたが、認証後は取得できたトークンを利用して、AWS側のリソースへアクセスすることができます。API GatewayのオーソライザーにCognitoユーザープールを設定して実装しましょう。

Discussion

akihiro1001akihiro1001

FlutterFlowにはAmplify SDKを組み込めません。

少しお伺いさせてください。
FlutterFlowではカスタムコードが書けますが、カスタムコードを利用してもAmplify SDKを組み込めないということでしょうか?
もしそうでない場合、カスタムコードで直接Amplify SDKを利用しないと判断された理由をお伺いしたいです。

GenAiエンジニアブログGenAiエンジニアブログ

@akihiro1001 さん
コメントありがとうございます。
当時、カスタムコードを利用してもAmplify SDKを組み込むのが難しいと判断した記憶があります。
CustomWidgetなどは基本的にmain.dartなどのFlutterFlowで自由に変更できないファイルに、設定が必要なライブラリは対応できません。

Amplify SDKはmain.dartも含め変更する必要があったことと、本記事の対応方法で要件を満たせるため利用しない判断としています。
もしかしたらアップデートでできるようになっているかもしれないので、興味あれば検証してみてください。