AWS Amplify で Amazon Cognito のパスワードレス認証が利用できるようになりました

AWS Amplify で Amazon Cognito のパスワードレス認証が利用できるようになりました

Clock Icon2024.11.29

こんにちは、森田です。

以下のアップデートで AWS Amplify で Amazon Cognito のパスワードレス認証が利用できるようになりました。

https://aws.amazon.com/about-aws/whats-new/2024/11/aws-amplify-passwordless-authentication-amazon-cognito/

本記事では、実際にメールを利用したパスワードレス認証を試してみたいと思います。

やってみた

前提条件

以下のクイックスタート(Next.js App Router)を実施済みとします。

https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components/

Amplify のバージョン変更

クイックスタートの Amplify のバージョンは最新でないため、以下を実行して変更します。

npm i aws-amplify@6.10.0

Amazon Cognito でパスワードレス認証の有効化

次に、コンソールで Amazon Cognito のパスワードレス認証を有効にします。」

通常、Cognitoの設定値は、defineAuthから変更できるのですが、現在はサポートしていないようです。

https://docs.amplify.aws/react/build-a-backend/auth/concepts/passwordless/

Warning: Passwordless configuration is currently not available in defineAuth

Amplifyで作成されたユーザープールを選択し、サインインの設定ページを開きます。

デフォルトでは、以下のようにパスワードレス認証は無効なので、有効化します。

img_44.png

今回は、「メールメッセージのワンタイムパスワード」を選択します。

img.png

コードの作成

続いて、サインインページを作成します。

コード全文
app/signin/page.tsx
"use client";

import { useState } from "react";
import { Amplify } from "aws-amplify";
import { signIn, confirmSignIn } from "aws-amplify/auth";
import outputs from "@/amplify_outputs.json";

Amplify.configure(outputs);

export default function App() {
  const [email, setEmail] = useState(""); // メールアドレスの状態
  const [otpCode, setOtpCode] = useState(""); // OTPコードの状態
  const [isCodeSent, setIsCodeSent] = useState(false); // コード送信済みかどうか
  const [isSignedIn, setIsSignedIn] = useState(false); // サインイン済みかどうか
  const [error, setError] = useState(""); // エラーメッセージ

  // メールアドレスでサインイン開始(OTP送信)
  const handleSendCode = async () => {
    try {
      const { nextStep: signInNextStep } = await signIn({
        username: email,
        options: {
          authFlowType: "USER_AUTH",
          preferredChallenge: "EMAIL_OTP",
        },
      });

      if (signInNextStep.signInStep === "CONFIRM_SIGN_IN_WITH_EMAIL_CODE") {
        setIsCodeSent(true); // コード送信済み状態に
        setError(""); // エラーをリセット
      }
    } catch (err) {
      setError("コード送信中にエラーが発生しました");
    }
  };

  // 確認コードでサインイン完了
  const handleConfirmSignIn = async () => {
    try {
      const { nextStep: confirmSignInNextStep } = await confirmSignIn({
        challengeResponse: otpCode,
      });

      if (confirmSignInNextStep.signInStep === "DONE") {
        setIsSignedIn(true); // サインイン済み状態に
        setError("");
      }
    } catch (err) {
      setError("サインイン中にエラーが発生しました");
    }
  };

  const styles = {
    container: {
      fontFamily: "Arial, sans-serif",
      textAlign: "center",
      backgroundColor: "#f3f4f6",
      padding: "50px",
      borderRadius: "10px",
      width: "350px",
      margin: "100px auto",
      boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
    },
    heading: {
      marginBottom: "20px",
      fontSize: "24px",
      color: "#333",
    },
    inputGroup: {
      display: "flex",
      alignItems: "center",
      marginBottom: "20px",
      gap: "10px",
    },
    input: {
      flex: 1,
      padding: "10px",
      borderRadius: "5px",
      border: "1px solid #ccc",
    },
    button: {
      padding: "10px 20px",
      backgroundColor: "#4CAF50",
      color: "white",
      border: "none",
      borderRadius: "5px",
      cursor: "pointer",
      fontWeight: "bold",
    },
    successMessage: {
      color: "green",
      fontSize: "18px",
      fontWeight: "bold",
    },
    error: {
      color: "red",
      marginTop: "10px",
    },
  };

  return (
    <div>
      <h1 style={styles.heading}>Passwordless Sign In</h1>
      {!isSignedIn ? (
        !isCodeSent ? (
          <div style={styles.inputGroup}>
            <input
              style={styles.input}
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="メールアドレスを入力"
            />
            <button
              style={styles.button}
              onClick={handleSendCode}
              disabled={!email}
            >
              コードを送信
            </button>
          </div>
        ) : (
          <div style={styles.inputGroup}>
            <input
              style={styles.input}
              type="text"
              value={otpCode}
              onChange={(e) => setOtpCode(e.target.value)}
              placeholder="確認コードを入力"
            />
            <button
              style={styles.button}
              onClick={handleConfirmSignIn}
              disabled={!otpCode}
            >
              サインイン
            </button>
          </div>
        )
      ) : (
        <p style={styles.successMessage}>サインインに成功しました!</p>
      )}
      {error && <p style={styles.error}>{error}</p>}
    </div>
  );
}

パスワードレスの指定

signIn関数のpreferredChallengeにどのタイプのパスワードレスを利用するかを指定します。

const { nextStep: signInNextStep } = await signIn({
    username: email,
    options: {
        authFlowType: "USER_AUTH",
        preferredChallenge: "EMAIL_OTP",
    },
});

他にも以下が指定できます。

  • SMS OTP
  • WebAuthn Passkeys

https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-in/#password

動作確認

では、実際にアプリケーションを動かして、動作確認してみます。

npm run dev

アプリケーション起動後、http://localhost:3000/signinにアクセスします。

メールアドレスを入力し、「コードを送信」をクリックします。

img.png

しばらくすると、メールアドレスに認証コードが送信されます。

img.png

このコードをアプリケーション上で入力し、「サインイン」をクリックします。

img.png

無事にサインインできました!

img.png

さいごに

かなり簡単にパスワードレス認証を試すことができました。

現状は、手動で Cognito でパスワードレス認証の有効化を行う必要があるため、今後defineAuthでサポートされることを期待しています。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.