React + Cognito + Amplifyでログイン認証機能を作成する方法

CognitoとAmplifyとは

フロントエンドエンジニアならReactを知らない人はいませんが、CognitoとAmplifyは知らない人が多いので簡単に説明します。

CognitoはAWSのユーザー認証サービス、AmplifyはCognitoの認証機能やUIなどを簡単に組み込むためのものです。

以降では新規作成でメールアドレスとパスワードを入力して、パスワードおよび二要素認証でログインする画面のサンプルを作成します。

ログイン後の画面には登録したメールアドレスおよびサインアウト(ログアウト)ボタンを表示します。

ログイン後の画面

ログイン認証機能の作成手順

まず、以下のコマンドでReactの環境を用意します。

npm create vite@latest my-react-cognito -- --template react-swc-ts

npm createが完了したら、インストールして起動するか確認してください。

cd my-react-cognito
npm install
npm run dev

次に、「Amazon Cognito」でユーザープールを作成します。

ユーザープールを作成したらAmplify UIをインストールします。

npm i aws-amplify @aws-amplify/ui-react 

これらの作業が完了したら、main.tsxとApp.tsxを以下のコードに書き換えれば、ログイン画面の実装が完了します。

userPoolIdはユーザープール情報、userPoolClientIdはアプリケーションクライアントに関する情報にあるので、それぞれコピーして下記のコードの場所に貼り付けます。

ユーザープール情報
main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import { Amplify } from 'aws-amplify'

Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: "us-east-1_zBR2FyXYZ",
      userPoolClientId: "16ec9v0pr7eb1bm9o34d09m4sn",
      loginWith: {
        email: true
      }
    },
  }
})

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)
App.tsx
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

function App() {
  return (
    <Authenticator>
      {({ signOut, user }) => (
        <main>
          <h1>メールアドレス: {user?.signInDetails?.loginId}</h1>
          <button onClick={signOut}>Sign out</button>
        </main>
      )}
    </Authenticator>
  )
}

export default App

ここまで完了したら、npm run devを実行してみてください。

下図のようなログイン画面が表示されます。

React + Cognito + Amplifyでログイン認証機能を作成する方法

実際にCreate Accountからメールアドレスとパスワードを入力してアカウントを作成して、ログインすると以下のようなログイン後の画面が表示されます。

ログイン後の画面

ここでログアウトボタンを押すとログアウトされ、ログイン画面に戻ります。

Amplify UIの英語を日本語にする

Amplify UIのテキストは英語で表示されているため、英語が読めないユーザーのために日本語に翻訳する必要があります。

日本語化するにはi18n.tsファイルを用意して、以下のコードにしてApp.tsxに読み込みます。

Sign In・Sign Outは日本人にはあまり馴染みがないため、サインイン・サインアウトではなく、ログイン・ログアウトと翻訳したほうが良いです。

i18n.ts
import { I18n } from 'aws-amplify/utils'

I18n.putVocabularies({
  ja: {
    'Account recovery requires verified contact information':
      'アカウントの復旧には確認済みの連絡先が必要です',
    'An account with the given email already exists.':
      '入力されたメールアドレスのアカウントが既に存在します',
    'Back to Sign In': 'ログインに戻る',
    'Change Password': 'パスワードを変える',
    Code: '認証コード',
    'Code *': '認証コード',
    Confirm: '確認',
    Confirming: '確認中',
    'Confirm a Code': '認証コードを確認',
    'Confirm TOTP Code': '認証コードを確認',
    'Setup TOTP': '認証コードを設定',
    'Confirm Password': 'パスワードの確認',
    'Confirm Sign In': 'ログインする',
    'Confirm Sign Up': '登録する',
    'Confirmation Code': '認証コード',
    'Create a new account': '新しいアカウント作成',
    'Create account': 'アカウント作成',
    'Create Account': 'アカウント作成',
    Email: 'メールアドレス',
    'Enter your code': '認証コードを入力',
    'Enter your Email': 'メールアドレスを入力',
    'Enter your Password': 'パスワードを入力',
    'Your passwords must match': 'パスワードが一致していません',
    'Password must have at least 8 characters': 'パスワードは8文字以上で入力してください',
    'Password must include at least one number': 'パスワードには数字を1つ以上含めてください',
    'Password did not conform with policy: Password must have uppercase characters': 'パスワードがポリシーに準拠していません: パスワードには大文字を使用する必要があります',
    'Password did not conform with policy: Password must have numeric characters': 'パスワードがポリシーに準拠していません: パスワードには数字を含める必要があります',
    'Password did not conform with policy: Password must have symbol characters': 'パスワードがポリシーに準拠していません: パスワードには記号文字を含める必要があります',
    'Incorrect username or password.': 'ユーザー名またはパスワードが間違っています',
    'User does not exist.': 'ユーザーが存在しません',
    'Please confirm your Password': '確認用パスワードを入力',
    'Enter your username': 'ユーザー名を入力',
    'Forgot Password': 'パスワードを忘れた',
    'Forgot your password?': 'パスワードを忘れましたか?',
    'Have an account? ': 'アカウントを持っていますか?',
    'Incorrect username or password': 'ユーザー名かパスワードが異なります',
    'Invalid password format': 'パスワードの形式が無効です',
    'Invalid phone number format':
      '不正な電話番号の形式です。\n+12345678900 の形式で入力してください',
    'Lost your code? ': '認証コードを失くしましたか?',
    'New Password': '新しいパスワード',
    'No account? ': 'アカウントが無いとき',
    or: '又は',
    Password: 'パスワード',
    'Password attempts exceeded': 'ログインの試行回数が上限に達しました',
    'Phone Number': '認証電話番号',
    'Resend Code': '認証コードを再送信',
    'Reset password': 'パスワードをリセット',
    'Reset your password': 'パスワードをリセットする',
    'Send Code': '認証コードを送信',
    'Sign in': 'ログイン',
    'Sign In': 'ログイン',
    'Sign in to your account': 'アカウントにログイン',
    'Sign In with Amazon': 'Amazonでログイン',
    'Sign In with Facebook': 'Facebookでログイン',
    'Sign In with Google': 'Googleでログイン',
    'Sign Out': 'ログアウト',
    'Sign Up': '登録',
    'Signing in': 'ログイン中',
    Skip: 'スキップ',
    Submit: '送信',
    'User already exists': '既にユーザーが存在しています',
    'User does not exist': 'ユーザーが存在しません',
    Username: 'ユーザー名',
    'Username cannot be empty': 'ユーザー名は入力必須です',
    Verify: '確認',
    'Verify Contact': '連絡先を確認',
    'We Emailed You': '認証コードを送信しました',
    'Your code is on the way. To log in, enter the code we emailed to':
      'ログインするには、メールに記載された認証コードを入力してください。送信先:',
    'Your code is on the way. To log in, enter the code we texted to':
      'ログインするには、テキストメッセージに記載された認証コードを入力してください。送信先:',
    'It may take a minute to arrive.':
      '認証コードを受信するまで数分かかる場合があります。',
  },
})

I18n.setLanguage('ja')

AuthenticatorのformFieldsで上書きしていって翻訳することもできますが、コード量が多くなってしまうので非推奨です。

App.tsx
<Authenticator
  formFields={{
    signUp: {
      email: {
        label: 'メールアドレス',
        placeholder: 'メールアドレスを入力',
        order: 1
      },
      password: {
        label: 'パスワード',
        placeholder: 'パスワードを入力',
        order: 2
      },
      confirm_password: {
        label: '確認用パスワード',
        placeholder: '確認用パスワードを入力',
        order: 3
      },
    }
  }}
>

アカウント作成後に表示される「It may take a minute to arrive.」はi18n.tsファイルで翻訳できないので、以下のようにcomponentsのConfirmSignUpのHeader()で日本語を追記して、元の英語はCSSで非表示にする必要があります。

App.tsx
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import './App.css';
import './i18n'

function App() {
  return (
    <Authenticator
      components={{
        ConfirmSignUp: {
          Header() {
            return (
              <>
                <h2 style={{ margin: 0 }}>メールを送信しました</h2>
                <p>
                  ログインするには件名が「iwb.jpの認証コードのお知らせ」の<br />
                  メールに記載された認証コードを入力してください。<br />
                  メールが届くまで1分ほどかかる場合があります。
                </p>
              </>
            );
          },
        },
      }}
    >
      {({ signOut, user }) => (
        <main>
          <h1>メールアドレス: {user?.signInDetails?.loginId}</h1>
          <button onClick={signOut}>ログアウト</button>
        </main>
      )}
    </Authenticator>
  )
}

export default App
App.css
[data-amplify-authenticator-confirmsignup] .amplify-authenticator__subtitle {
  display: none;
}

アカウント作成後のEメールメッセージも日本語にする

Cognitoではデフォルトの設定だとログイン画面だけでなく、アカウント作成後の認証コードのEメールメッセージも英語になっています。

英語だとメールの件名やメッセージ文が英語となっており、わかりづらいため日本語に設定したほうが良いです。

Eメールメッセージの変更はユーザープールの「メッセージテンプレート」から変更できます。

Cognito メッセージテンプレート

Eメールメッセージの改行は<br>なので注意が必要です。

このようにpタグとh1タグを使用してフォントサイズを変えるとユーザーが認証コードを見やすくなります。

HTML
<p>以下はログインに必要な認証コードです。<p>
<h1>認証コード:{####}<h1>
Cognito 認証コードをGmailで受け取ったときの表示

variation="modal" で見た目をモーダル風にする

<Authenticator />に「variation="modal"」を追記すると見た目がモーダル風になり、上下左右中央に表示されます。

後述するHeader()とFooter()を使用したロゴ画像およびcopyrightの追加を行う場合は、グレー背景の部分に表示されてしまうため、「variation="modal"」は使用しないほうが良いです。

アカウント作成後の確認画面を日本語化

ちなみにbodyに「display: grid;」と「place-items: center;」の追加でも上下左右中央に表示できます。

App.css
body {
  display: grid;
  place-items: center;
}

ヘッダーとフッターを追加する

<Authenticator />のcomponentsにHeader()やFooter()を使うことで、下図のようにヘッダーとフッターを追加できます。

ロゴ画像やcopyrightをログイン画面に追加したい場合はこちらを使用します。

App.tsx
function App() {
  return (
    <Authenticator
      components={{
        Header() {
          return <h1>Hello</h1>;
        },
        Footer() {
          return <p className="copyright">© iwb.jp</p>;
        },
        // 以下略
ログイン認証画面でヘッダーとフッターを追加

アカウント作成を非表示にする

<Authenticator />に「hideSignUp」を追記すると、アカウント作成を非表示にできます。

会社内でのみ使用するWebサイトで、管理者のみがCognitoでアカウント作成を行う場合はこの設定でアカウント作成ができないようにします。

ログイン認証画面でアカウント作成を非表示にした設定

ボタンやリンクの色を変更

ThemeProviderを読み込んでthemeで色を指定して以下のように適用します。

ボタンは <Button /> が使われていなければCSSで直接色を指定して変更します。

App.tsx
import {
  Authenticator,
  ThemeProvider,
} from '@aws-amplify/ui-react'
import type { Theme } from '@aws-amplify/ui-react'
import '@aws-amplify/ui-react/styles.css'
import './App.css'
import './i18n'

const theme: Theme = {
  name: 'my-theme',
  tokens: {
    colors: {
      font: {
        primary: { value: '#1E1E1E' }, // フォント色
        interactive: { value: '#CC0000' }, // リンク色
      },
    },
  },
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Authenticator>
        {({ signOut, user }) => (
          <main>
            <h1>メールアドレス: {user?.signInDetails?.loginId}</h1>
            <button onClick={signOut}>ログアウト</button>
          </main>
        )}
      </Authenticator>
    </ThemeProvider>
  )
}

export default App
App.css
.amplify-button[type="submit"] {
  background: #CC0000;

  &:hover {
    background: #AA0000;
  }
}
Amplifyのログイン認証のボタンやリンクの色を赤に変更