🌀

AWS cognitoとReactでログインを実装する

2020/12/03に公開

こんにちはハトです。業務でcognitoを利用していたのですがかなり躓いたので共有します。

つまづきポイント多すぎ

まずAWSを利用している方でSPAで認証を実現しようとしてネットをあさるといろいろ情報があることに気づく

  • cognitoの利用
  • Amplify の auth の利用

わけわかめとなる。次にAmplifyを調べる

  • cliがあるよ
  • cliでコマンド打てばGraphQLとか使えるよ
  • Amplify Consoleあるよ。静的ホスティングが自動化できるよ

ん?認証だけ使いたいのになんでGraphQLいれるの?みたいになる。

とりあえず、僕がつまずいたポイントや知見を共有しようと思います。

前提

すでにcreate-react-appなどで、何かしらのreactプロジェクトを作ってある。

AWSでconigtoを使うだけならamplifyはいらないけど、amplifyライブラリは便利だよ

厳密にはamplifyのcliは必要ないけどamplifyライブラリは便利です。

まずは次の点を理解してください。

  • amplifyはいろいろなサービスを複合したものである。それぞれをカテゴリという。
  • カテゴリにはauthみたいな名前付いてるけど、結局中身はcognitoとかS3とか他のサービス。
  • cliコマンドを利用して各カテゴリの環境構築を簡単に行えることが売り。だからかドキュメントにはかならずcliコマンドから始める風に書いてある。これが初心者を戸惑わせる。
  • amplifyのcliとreactなどで実際に使うamplifyライブラリは別物。
  • amplifyのcliコマンドを使って環境構築しなくてもamplifyライブラリは使える。
  • amplifyライブラリはマジ便利

amplifyのドキュメントはとりあえずcliから始めてますが、それだとamplifyの理解とcliの理解が必要になります。一気に理解するのはしんどいため、とりあえずreactでなんか流行ってるamplify使って認証を実現したいかたはcliをスキップするとよいでしょう。後のセクションではそれを行います。

amplifyは既存のcognitoは使えないよ

cognitoは先に作っていました。そのあとamplifyのcliを使いamplify add authをしたら、完全に新しいcognitoが作られており、どうやら既存のcognitoは使えないとのことでした。既存のcognitoを使いたい場合は、別途アプリケーションから設定する必要があります。

import Amplify from 'aws-amplify';

// 既存のcognitoの設定
Amplify.configure({
  aws_project_region: process.env.REACT_APP_AWS_PROJECT_REGION,
  aws_cognito_identity_pool_id:
    process.env.REACT_APP_AWS_COGNITO_IDENTITY_POOL_ID,
  aws_cognito_region: process.env.REACT_APP_AWS_COGNITO_REGION,
  aws_user_pools_id: process.env.REACT_APP_AWS_USER_POOLS_ID,
  aws_user_pools_web_client_id: process.env.REACT_APP_AWS_USER_POOLS_CLIENT_ID,
});

!!追記!!

調べてみたらようやく既存のcognitoを使えるようになっていました。
https://aws.amazon.com/jp/about-aws/whats-new/2020/10/use-existing-cognito-user-pools-identity-pools-for-amplify-project/

Reactにamplifyライブラリをinstallする

amplifyのcliを使わずに、amplifyの認証ライブラリを利用しようと思います。

まずは、既存のreactアプリにamplifyのライブラリをinstallします。

npm install aws-amplify

つぎにApp.tsxで設定を書けば使えるようになります。

App.tsxもしくはApp.jsx

import Amplify from 'aws-amplify';
import { cognitoConstants } from 'constants/auth';

Amplify.configure(cognitoConstants);

環境変数にあらかじめ設定値をいれておく。identity_poolなどは使わなければ空でよい。

constants/auth

export const cognitoConstants = {
  aws_project_region: process.env.REACT_APP_AWS_PROJECT_REGION,
  aws_cognito_identity_pool_id:
    process.env.REACT_APP_AWS_COGNITO_IDENTITY_POOL_ID,
  aws_cognito_region: process.env.REACT_APP_AWS_COGNITO_REGION,
  aws_user_pools_id: process.env.REACT_APP_AWS_USER_POOLS_ID,
  aws_user_pools_web_client_id:  process.env.REACT_APP_AWS_USER_POOLS_CLIENT_ID,
};

おそらくドキュメントは次のようになっているのではないでしょうか?

import Amplify from 'aws-amplify';
import awsconfig from './aws-exports'; // ここが違う
Amplify.configure(awsconfig);

./aws-exportsというファイルはamplify cliコマンドを実行したときに吐き出されるファイルです。amplify cliで生成された環境の設定値がのっています。今回はcliを使わない想定で作ったのでここを自分で設定しました。

次はUIとログインapiの実装部分ですが、ここで2つの道があります。

  1. 提供されているUIライブラリを使う
  2. UIは自身で作って、裏側のcognitoとの通信だけamplifyのauthライブラリに委託する。

1.提供されているUIライブラリを使う

1はめちゃくちゃ簡単です。

aws-amplifyのほかにUIライブラリをinstallします。

npm install @aws-amplify/ui-react

ちなみに、ややこしいことにaws-amplify-react@aws-amplify/ui-react2つのライブラリがありますが、@aws-amplify/ui-reactのほうが新しい方です。ネットには2つのライブラリのコードが混じって記事になっていますので、ご注意ください。

公式サイトをちゃんと参考にしてね。

App.tsxにて次のように設定します。やり方は2つあります。

つぎのようにwithAuthenticatorhocを利用するか。

import React from 'react';
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';
import Amplify from 'aws-amplify';
import { cognitoConstants } from 'constants/auth';

Amplify.configure(cognitoConstants);

const App = () => (
  <div>
    <AmplifySignOut />
    My App
  </div>
);

export default withAuthenticator(App);

AmplifyAuthenticatorで挟むかです。

import React from "react";
import Amplify from "aws-amplify";
import {AmplifyAuthenticator, AmplifySignOut} from "@aws-amplify/ui-react";
import Amplify from 'aws-amplify';
import { cognitoConstants } from 'constants/auth';

Amplify.configure(cognitoConstants);

const App = () => (
  <AmplifyAuthenticator>
    <div>
      My App
      <AmplifySignOut />
    </div>
  </AmplifyAuthenticator>
);

違いは、設定できるプロパティがAmplifyAuthenticatorのほうが豊富なことです。すぐデフォルトで使いたい方はwithAuthenticatorを使えば良いと思います。ただ公式ドキュメント的にはAmplifyAuthenticatorをよく使っているみたいですね。

公式推奨の方法。

ログインしているときは、通常のレイアウト、ログインしていないときはAmplifyAuthenticatorのレイアウトにする。

import React from 'react';
import './App.css';
import Amplify from 'aws-amplify';
import { AmplifyAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';
import { AuthState, onAuthUIStateChange } from '@aws-amplify/ui-components';
import { cognitoConstants } from 'constants/auth';

Amplify.configure(cognitoConstants);

const AuthStateApp: React.FunctionComponent = () => {
    const [authState, setAuthState] = React.useState<AuthState>();
    const [user, setUser] = React.useState<object | undefined>();

    React.useEffect(() => {
        return onAuthUIStateChange((nextAuthState, authData) => {
            setAuthState(nextAuthState);
            setUser(authData)
        });
    }, []);

  return authState === AuthState.SignedIn && user ? (
      <div className="App">
          <div>Hello, {user.username}</div>
          <AmplifySignOut />
      </div>
  ) : (
      <AmplifyAuthenticator />
  );
}

export default AuthStateApp;

これだけでデフォルトのUIとともにログイン機構ができます。すごいですね。

提供されているUIテーマの色を変更する

簡単です。グローバルcssに追加します。

index.css

:root {
  --amplify-primary-color: #ff6347;
  --amplify-primary-tint: #ff7359;
  --amplify-primary-shade: #e0573e;
}

amplify-sign-in {
  --footer-color: cyan;
}

どのコンポーネントがどのcss変数名に対応するのかは、公式サイトを御覧ください。

コンポーネントのカスタマイズ(例えば、サインアップフォームの設定など)

明示的にカスタマイズしたいコンポーネントをAmplifyAuthenticatorに入れてください。その際にslotに対応する文字列をいれる必要があります。

import React from 'react';
import './App.css';
import Amplify from 'aws-amplify';
import { AmplifyAuthenticator, AmplifySignUp, AmplifySignOut } from '@aws-amplify/ui-react';
import { AuthState, onAuthUIStateChange } from '@aws-amplify/ui-components';
import { cognitoConstants } from 'constants/auth';

Amplify.configure(cognitoConstants);

const AuthStateApp = () => {
    const [authState, setAuthState] = React.useState();
    const [user, setUser] = React.useState();

    React.useEffect(() => {
        return onAuthUIStateChange((nextAuthState, authData) => {
            setAuthState(nextAuthState);
            setUser(authData)
        });
    }, []);

  return authState === AuthState.SignedIn && user ? (
      <div className="App">
          <div>Hello, {user.username}</div>
          <AmplifySignOut />
      </div>
    ) : (
      <AmplifyAuthenticator>
        <AmplifySignUp
          slot="sign-up"
          formFields={[
            { type: "username" },
            { type: "password" },
            { type: "email" }
          ]}
        />
      </AmplifyAuthenticator>
  );
}

export default AuthStateApp;

2.UIは自身で作って、裏側のcognitoとの通信だけamplifyのauthライブラリに委託する

UIライブラリ@aws-amplify/ui-reactをinstallしない方法です。amplify Authライブラリでゴリゴリに書きます。

amplify-authのスターターキットのコードが参考になると思います(丸投げ)

もしくはこちらです。

こちらのよいところは、routingがやりやすいことですね。あとはログインなどに伴って独自の処理をもたせたいときなどです。

!!追記!!
Authライブラリの使い方を記事にしました!
https://zenn.dev/dove/articles/bb062581280b8d

ログイン状態をreduxで管理する

茨の道でした。興味ある方がいればもう少し書こうかなと思います。
ちなみにログイン状態をreduxで管理しなくても、Authライブラリを使えば現セッションのユーザー情報やいまユーザーがログインしているかどうかも調べることができます。

また、amplifyのHubライブラリを使えばpub/subができreduxいらないです。

僕自身は、最初にreduxを導入していたのと、コードのあちこちでamplify特有(ベンダー固有)のAuthが散らばるのが嫌で、reduxでログイン状態を管理することにしました。

茨の道でした。

日本語化する

エラー文を日本語化したいと思いこちらのサイトをまんま参考にしました。

UIの日本語化ふくめるのであればこちらのサイトのほうが参考になるかも。

App.tsx

import Amplify, { I18n } from 'aws-amplify';
import { cognitoConstants } from 'constants/auth'; // これさっきと一緒
import { COGNITO_ERROR } from 'constants/i18n';

// cognito エラーの日本語化
I18n.putVocabularies(COGNITO_ERROR);
I18n.setLanguage('ja');

Amplify.configure(cognitoConstants);

constants/i18n

export const COGNITO_ERROR = {
  ja: {
    'User does not exist.': 'ユーザーが存在しません',
    'Incorrect username or password.': 'ユーザー名またはパスワードが違います',
    'User is not confirmed.': 'ユーザーは検証されていません',
    'User already exists': 'ユーザーは既に存在します',
    'Invalid verification code provided, please try again.':
      '指定された確認コードが無効です。もう一度お試しください',
    'Invalid password format': 'パスワードのフォーマットが不正です',
    'Account recovery requires verified contact information':
      'アカウントの復元には確認済みの連絡先情報が必要です',
    'Invalid phone number format':
      '不正な電話番号フォーマットです。 電話番号は次のフォーマットで入力してください: +12345678900',
    'An account with the given email already exists.':
      'そのメールアドレスは既に存在します',
    'Username cannot be empty': 'ユーザー名は必須です',
    'Password attempts exceeded': 'パスワード試行回数が超過しました',
    'Attempt limit exceeded, please try after some time.':
      '試行制限を超過しました。しばらくしてからもう一度お試しください',
    'Username/client id combination not found.': 'ユーザーが存在しません',
    'CUSTOM_AUTH is not enabled for the client.': 'パスワードは必須です', // 本来の意味とは異なるが、パスワード未入力時に発生するのでこの訳としている
    'Password does not conform to policy: Password not long enough':
      'パスワードは8文字以上を入力してください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
    'Password does not conform to policy: Password must have uppercase characters':
      'パスワードには大文字を含めてください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
    'Password does not conform to policy: Password must have lowercase characters':
      'パスワードには小文字を含めてください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
    'Password does not conform to policy: Password must have numeric characters':
      'パスワードには数字を含めてください (8文字以上の大文字小文字を含む英数字)', // 適宜修正
    "1 validation error detected: Value at 'password' failed to satisfy constraint: Member must have length greater than or equal to 6":
      'パスワードは8文字以上、大文字小文字を含む英数字を指定してください', // 適宜修正。本来の意味とは異なるがこれで明示している。
    "2 validation errors detected: Value at 'password' failed to satisfy constraint: Member must have length greater than or equal to 6; Value at 'password' failed to satisfy constraint: Member must satisfy regular expression pattern: ^[S]+.*[S]+$":
      'パスワードは8文字以上、大文字小文字を含む英数字を指定してください', // 適宜修正。本来の意味とは異なるがこれで明示している。
  },
};

cognitoつまづきポイント

基本的に設定は変更できないから、本番に適用する前に十分に検証すること

本番に適用してしまったあとに設定を変更する必要がある場合の救済措置はある。それはcognitoのmigrationトリガーを利用すること。このトリガーを有効にすると、もしログイン時にuserpoolにusernameが存在しなければこのトリガーが発動される。トリガー先にはlambdaを設定する。lambdaにusernameとpasswordが渡されるので、既存のuserpoolにアクセスして、ユーザー情報を返して上げると、cognitoが自動的にユーザーを登録してくれる。

lambdaをどう書くかなどは、神記事のこちらが詳しい

注意点として、移行期間中はauthenticationFlowTypeはあまりセキュアでないUSER_PASSWORD_AUTHにする必要がある。

export const cognitoConstants = {
  aws_project_region: process.env.REACT_APP_AWS_PROJECT_REGION,
  aws_cognito_identity_pool_id:
    process.env.REACT_APP_AWS_COGNITO_IDENTITY_POOL_ID,
  aws_cognito_region: process.env.REACT_APP_AWS_COGNITO_REGION,
  aws_user_pools_id: process.env.REACT_APP_AWS_USER_POOLS_ID,
  aws_user_pools_web_client_id: process.env.REACT_APP_AWS_USER_POOLS_CLIENT_ID,
  authenticationFlowType: process.env.REACT_APP_AWS_COGNITO_FLOW_TYPE, // USER_PASSWORD_AUTHにする
};

usernameには基本的にメールなどでaliasできるように設定する

usernameのエイリアス設定忘れると、usernameにemailが設定される。usernameは基本的に変更不可なので、メールアドレスが変更できず詰むことに。

注意: ここもしメールアドレスを変更できる方法をしっているかたがいらっしゃいましたら教えて下さい。

メアド変更するとログインできなくなる可能性がある

こちらで詳しく解説しています。
https://zenn.dev/dove/articles/78ecf08b51ee0c

amplify Authライブラリについて

amazon-cognito-identity-jsのラップライブラリですね。

ややこしいことに似たメソッドが多くあります。かんたんな認識としてadminという名前がついているメソッドは基本的にサーバサイドで使うことが多いとだけ覚えておくとよいかと思います。

時間があればここをもう少し充実させます。

-> 充実させました!
https://zenn.dev/dove/articles/bb062581280b8d

手動でカスタマイズしたい猛者へ参考サイト

スターターキット。多分これを参考にするのが正解なきがする。
https://github.com/aws-samples/aws-amplify-auth-starters/tree/react/src

普通に導入したやつ
https://github.com/brayoh/react-amplify

reduxとの統合
https://jasonwatmore.com/post/2020/03/02/react-hooks-redux-user-registration-and-login-tutorial-example
https://github.com/ganeshmani/react-redux-typescript-example

Discussion