사용자가 이메일을 변경할 수 있도록 허용하기

우리는 Serverless 노트 앱의 사용자들이 이메일을 변경할 수 있도록 하고 싶습니다. 우리는 사용자 관리를 위해 Cognito를 사용하고 있으며, React 앱에서 AWS Amplify를 사용하고 있습니다. 이번 장에서는 이를 어떻게 구현할지 살펴보겠습니다.

참고로, 우리는 다음과 같은 포크된 버전의 노트 앱을 사용하고 있습니다:

이전 장에서 /settings/email로 연결되는 설정 페이지를 만들었습니다. 이제 이를 구현해 보겠습니다.

이메일 변경 폼

Change indicator src/containers/ChangeEmail.js에 다음 내용을 추가합니다.

import React, { useState } from "react";
import { Auth } from "aws-amplify";
import { useNavigate } from "react-router-dom";
import {
  FormText,
  FormGroup,
  FormControl,
  FormLabel,
} from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import { useFormFields } from "../lib/hooksLib";
import { onError } from "../lib/errorLib";
import "./ChangeEmail.css";

export default function ChangeEmail() {
  const nav = useNavigate();
  const [codeSent, setCodeSent] = useState(false);
  const [fields, handleFieldChange] = useFormFields({
    code: "",
    email: "",
  });
  const [isConfirming, setIsConfirming] = useState(false);
  const [isSendingCode, setIsSendingCode] = useState(false);

  function validateEmailForm() {
    return fields.email.length > 0;
  }

  function validateConfirmForm() {
    return fields.code.length > 0;
  }

  async function handleUpdateClick(event) {
    event.preventDefault();

    setIsSendingCode(true);

    try {
      const user = await Auth.currentAuthenticatedUser();
      await Auth.updateUserAttributes(user, { email: fields.email });
      setCodeSent(true);
    } catch (error) {
      onError(error);
      setIsSendingCode(false);
    }
  }

  async function handleConfirmClick(event) {
    event.preventDefault();

    setIsConfirming(true);

    try {
      await Auth.verifyCurrentUserAttributeSubmit("email", fields.code);

      nav("/settings");
    } catch (error) {
      onError(error);
      setIsConfirming(false);
    }
  }

  function renderUpdateForm() {
    return (
      <form onSubmit={handleUpdateClick}>
        <FormGroup bsSize="large" controlId="email">
          <FormLabel>Email</FormLabel>
          <FormControl
            autoFocus
            type="email"
            value={fields.email}
            onChange={handleFieldChange}
          />
        </FormGroup>
        <LoaderButton
          block
          type="submit"
          bsSize="large"
          isLoading={isSendingCode}
          disabled={!validateEmailForm()}
        >
          Update Email
        </LoaderButton>
      </form>
    );
  }

  function renderConfirmationForm() {
    return (
      <form onSubmit={handleConfirmClick}>
        <FormGroup bsSize="large" controlId="code">
          <FormLabel>Confirmation Code</FormLabel>
          <FormControl
            autoFocus
            type="tel"
            value={fields.code}
            onChange={handleFieldChange}
          />
          <FormText>
            Please check your email ({fields.email}) for the confirmation code.
          </FormText>
        </FormGroup>
        <LoaderButton
          block
          type="submit"
          bsSize="large"
          isLoading={isConfirming}
          disabled={!validateConfirmForm()}
        >
          Confirm
        </LoaderButton>
      </form>
    );
  }

  return (
    <div className="ChangeEmail">
      {!codeSent ? renderUpdateForm() : renderConfirmationForm()}
    </div>
  );
}

사용자 이메일 변경 흐름은 사용자 가입 방식과 매우 유사합니다.

  1. 사용자에게 새 이메일을 입력하도록 요청합니다.
  2. Cognito가 사용자에게 인증 코드를 보냅니다.
  3. 사용자가 코드를 입력하면 이메일 변경이 확인됩니다.

먼저 renderUpdateForm()에서 사용자에게 새 이메일을 입력하도록 요청하는 폼을 렌더링합니다. 사용자가 이 폼을 제출하면 다음을 호출합니다.

const user = await Auth.currentAuthenticatedUser();
Auth.updateUserAttributes(user, { email: fields.email });

이 코드는 현재 사용자를 가져와 Amplify의 Auth 모듈을 사용해 이메일을 업데이트합니다. 그런 다음 renderConfirmationForm()에서 코드를 입력할 수 있는 폼을 렌더링합니다. 이 폼을 제출하면 다음을 호출합니다.

Auth.verifyCurrentUserAttributeSubmit("email", fields.code);

이 코드는 Cognito 측에서 변경 사항을 확인합니다. 마지막으로 사용자를 설정 페이지로 리디렉션합니다.

Change indicator src/containers/ChangeEmail.css에 몇 가지 스타일을 추가합니다.

@media all and (min-width: 480px) {
  .ChangeEmail {
    padding: 60px 0;
  }

  .ChangeEmail form {
    margin: 0 auto;
    max-width: 320px;
  }
}

Change indicator 마지막으로 src/Routes.js에 새 페이지를 추가합니다.

<AuthenticatedRoute exact path="/settings/email">
  <ChangeEmail />
</AuthenticatedRoute>

Change indicator 그리고 헤더에서 이를 임포트합니다.

import ChangeEmail from "./containers/ChangeEmail";

이제 사용자가 이메일을 변경할 수 있습니다.

이메일 변경 페이지 스크린샷

세부 사항

사용자가 새 이메일을 확인하지 않으면 이메일 변경 흐름이 중단될 수 있습니다. 이 경우 이메일이 변경된 것처럼 보이지만 Cognito는 이를 확인되지 않은 상태로 표시합니다. 이 경우를 여러분이 직접 처리하도록 하겠지만, 이를 수행하는 방법에 대한 몇 가지 힌트를 제공합니다.

  • Auth.userAttributes(currentUser)를 호출하여 현재 사용자의 Cognito 속성을 가져올 수 있습니다. 이메일 속성을 찾고 attributes["email_verified"] !== "false"를 사용하여 확인되지 않았는지 확인합니다.

  • 이 경우 사용자가 확인 코드를 다시 보낼 수 있는 간단한 표시를 보여줍니다. Auth.verifyCurrentUserAttribute("email")을 호출하여 이를 수행할 수 있습니다.

  • 다음으로 위의 확인 코드 폼을 표시하고 Auth.verifyCurrentUserAttributeSubmit("email", fields.code)를 호출하여 동일한 흐름을 따를 수 있습니다.

이렇게 하면 이메일 변경 흐름이 더 견고해지고 사용자가 새 이메일을 확인하는 것을 잊은 경우를 처리할 수 있습니다.